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.widget.LauncherAppWidgetProviderInfo.CLS_CUSTOM_WIDGET_PREFIX; 20 21 import android.appwidget.AppWidgetManager; 22 import android.appwidget.AppWidgetProviderInfo; 23 import android.content.ComponentName; 24 import android.content.Context; 25 import android.os.Parcel; 26 import android.os.Process; 27 import android.util.SparseArray; 28 29 import androidx.annotation.NonNull; 30 import androidx.annotation.Nullable; 31 32 import com.android.launcher3.model.data.LauncherAppWidgetInfo; 33 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; 34 import com.android.launcher3.util.MainThreadInitializedObject; 35 import com.android.launcher3.util.PackageUserKey; 36 import com.android.launcher3.util.SafeCloseable; 37 import com.android.launcher3.widget.LauncherAppWidgetHostView; 38 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; 39 import com.android.systemui.plugins.CustomWidgetPlugin; 40 import com.android.systemui.plugins.PluginListener; 41 42 import java.util.ArrayList; 43 import java.util.List; 44 import java.util.function.Consumer; 45 import java.util.stream.Stream; 46 47 /** 48 * CustomWidgetManager handles custom widgets implemented as a plugin. 49 */ 50 public class CustomWidgetManager implements PluginListener<CustomWidgetPlugin>, SafeCloseable { 51 52 public static final MainThreadInitializedObject<CustomWidgetManager> INSTANCE = 53 new MainThreadInitializedObject<>(CustomWidgetManager::new); 54 55 private final Context mContext; 56 /** 57 * auto provider Id is an ever-increasing number that serves as the providerId whenever a new 58 * custom widget has been connected. 59 */ 60 private int mAutoProviderId = 0; 61 private final SparseArray<CustomWidgetPlugin> mPlugins; 62 private final List<CustomAppWidgetProviderInfo> mCustomWidgets; 63 private final SparseArray<ComponentName> mWidgetsIdMap; 64 private Consumer<PackageUserKey> mWidgetRefreshCallback; 65 CustomWidgetManager(Context context)66 private CustomWidgetManager(Context context) { 67 mContext = context; 68 mPlugins = new SparseArray<>(); 69 mCustomWidgets = new ArrayList<>(); 70 mWidgetsIdMap = new SparseArray<>(); 71 PluginManagerWrapper.INSTANCE.get(context) 72 .addPluginListener(this, CustomWidgetPlugin.class, true); 73 } 74 75 @Override close()76 public void close() { 77 PluginManagerWrapper.INSTANCE.get(mContext).removePluginListener(this); 78 } 79 80 @Override onPluginConnected(CustomWidgetPlugin plugin, Context context)81 public void onPluginConnected(CustomWidgetPlugin plugin, Context context) { 82 mPlugins.put(mAutoProviderId, plugin); 83 List<AppWidgetProviderInfo> providers = AppWidgetManager.getInstance(context) 84 .getInstalledProvidersForProfile(Process.myUserHandle()); 85 if (providers.isEmpty()) return; 86 Parcel parcel = Parcel.obtain(); 87 providers.get(0).writeToParcel(parcel, 0); 88 parcel.setDataPosition(0); 89 CustomAppWidgetProviderInfo info = newInfo(mAutoProviderId, plugin, parcel, context); 90 parcel.recycle(); 91 mCustomWidgets.add(info); 92 mWidgetsIdMap.put(mAutoProviderId, info.provider); 93 mWidgetRefreshCallback.accept(null); 94 mAutoProviderId++; 95 } 96 97 @Override onPluginDisconnected(CustomWidgetPlugin plugin)98 public void onPluginDisconnected(CustomWidgetPlugin plugin) { 99 int providerId = findProviderId(plugin); 100 if (providerId == -1) return; 101 mPlugins.remove(providerId); 102 mCustomWidgets.remove(getWidgetProvider(providerId)); 103 mWidgetsIdMap.remove(providerId); 104 } 105 106 /** 107 * Inject a callback function to refresh the widgets. 108 */ setWidgetRefreshCallback(Consumer<PackageUserKey> cb)109 public void setWidgetRefreshCallback(Consumer<PackageUserKey> cb) { 110 mWidgetRefreshCallback = cb; 111 } 112 113 /** 114 * Callback method to inform a plugin it's corresponding widget has been created. 115 */ onViewCreated(LauncherAppWidgetHostView view)116 public void onViewCreated(LauncherAppWidgetHostView view) { 117 CustomAppWidgetProviderInfo info = (CustomAppWidgetProviderInfo) view.getAppWidgetInfo(); 118 CustomWidgetPlugin plugin = mPlugins.get(info.providerId); 119 if (plugin == null) return; 120 plugin.onViewCreated(view); 121 } 122 123 /** 124 * Returns the stream of custom widgets. 125 */ 126 @NonNull stream()127 public Stream<CustomAppWidgetProviderInfo> stream() { 128 return mCustomWidgets.stream(); 129 } 130 131 /** 132 * Returns the widget id for a specific provider. 133 */ getWidgetIdForCustomProvider(@onNull ComponentName provider)134 public int getWidgetIdForCustomProvider(@NonNull ComponentName provider) { 135 int index = mWidgetsIdMap.indexOfValue(provider); 136 if (index >= 0) { 137 return LauncherAppWidgetInfo.CUSTOM_WIDGET_ID - mWidgetsIdMap.keyAt(index); 138 } else { 139 return AppWidgetManager.INVALID_APPWIDGET_ID; 140 } 141 } 142 143 /** 144 * Returns the widget provider in respect to given widget id. 145 */ 146 @Nullable getWidgetProvider(int widgetId)147 public LauncherAppWidgetProviderInfo getWidgetProvider(int widgetId) { 148 ComponentName cn = mWidgetsIdMap.get(LauncherAppWidgetInfo.CUSTOM_WIDGET_ID - widgetId); 149 for (LauncherAppWidgetProviderInfo info : mCustomWidgets) { 150 if (info.provider.equals(cn)) return info; 151 } 152 return null; 153 } 154 newInfo(int providerId, CustomWidgetPlugin plugin, Parcel parcel, Context context)155 private static CustomAppWidgetProviderInfo newInfo(int providerId, CustomWidgetPlugin plugin, 156 Parcel parcel, Context context) { 157 CustomAppWidgetProviderInfo info = new CustomAppWidgetProviderInfo( 158 parcel, false, providerId); 159 info.provider = new ComponentName( 160 context.getPackageName(), CLS_CUSTOM_WIDGET_PREFIX + providerId); 161 162 info.label = plugin.getLabel(); 163 info.resizeMode = plugin.getResizeMode(); 164 165 info.spanX = plugin.getSpanX(); 166 info.spanY = plugin.getSpanY(); 167 info.minSpanX = plugin.getMinSpanX(); 168 info.minSpanY = plugin.getMinSpanY(); 169 return info; 170 } 171 findProviderId(CustomWidgetPlugin plugin)172 private int findProviderId(CustomWidgetPlugin plugin) { 173 for (int i = 0; i < mPlugins.size(); i++) { 174 int providerId = mPlugins.keyAt(i); 175 if (mPlugins.get(providerId) == plugin) { 176 return providerId; 177 } 178 } 179 return -1; 180 } 181 } 182