/* * 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.widget.custom; import static com.android.launcher3.widget.LauncherAppWidgetProviderInfo.CLS_CUSTOM_WIDGET_PREFIX; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProviderInfo; import android.content.ComponentName; import android.content.Context; import android.os.Parcel; import android.os.Process; import android.util.SparseArray; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.launcher3.model.data.LauncherAppWidgetInfo; import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; import com.android.launcher3.util.MainThreadInitializedObject; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.SafeCloseable; import com.android.launcher3.widget.LauncherAppWidgetHostView; import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; import com.android.systemui.plugins.CustomWidgetPlugin; import com.android.systemui.plugins.PluginListener; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; import java.util.stream.Stream; /** * CustomWidgetManager handles custom widgets implemented as a plugin. */ public class CustomWidgetManager implements PluginListener, SafeCloseable { public static final MainThreadInitializedObject INSTANCE = new MainThreadInitializedObject<>(CustomWidgetManager::new); private final Context mContext; /** * auto provider Id is an ever-increasing number that serves as the providerId whenever a new * custom widget has been connected. */ private int mAutoProviderId = 0; private final SparseArray mPlugins; private final List mCustomWidgets; private final SparseArray mWidgetsIdMap; private Consumer mWidgetRefreshCallback; private CustomWidgetManager(Context context) { mContext = context; mPlugins = new SparseArray<>(); mCustomWidgets = new ArrayList<>(); mWidgetsIdMap = new SparseArray<>(); PluginManagerWrapper.INSTANCE.get(context) .addPluginListener(this, CustomWidgetPlugin.class, true); } @Override public void close() { PluginManagerWrapper.INSTANCE.get(mContext).removePluginListener(this); } @Override public void onPluginConnected(CustomWidgetPlugin plugin, Context context) { mPlugins.put(mAutoProviderId, plugin); List providers = AppWidgetManager.getInstance(context) .getInstalledProvidersForProfile(Process.myUserHandle()); if (providers.isEmpty()) return; Parcel parcel = Parcel.obtain(); providers.get(0).writeToParcel(parcel, 0); parcel.setDataPosition(0); CustomAppWidgetProviderInfo info = newInfo(mAutoProviderId, plugin, parcel, context); parcel.recycle(); mCustomWidgets.add(info); mWidgetsIdMap.put(mAutoProviderId, info.provider); mWidgetRefreshCallback.accept(null); mAutoProviderId++; } @Override public void onPluginDisconnected(CustomWidgetPlugin plugin) { int providerId = findProviderId(plugin); if (providerId == -1) return; mPlugins.remove(providerId); mCustomWidgets.remove(getWidgetProvider(providerId)); mWidgetsIdMap.remove(providerId); } /** * Inject a callback function to refresh the widgets. */ public void setWidgetRefreshCallback(Consumer cb) { mWidgetRefreshCallback = cb; } /** * Callback method to inform a plugin it's corresponding widget has been created. */ public void onViewCreated(LauncherAppWidgetHostView view) { CustomAppWidgetProviderInfo info = (CustomAppWidgetProviderInfo) view.getAppWidgetInfo(); CustomWidgetPlugin plugin = mPlugins.get(info.providerId); if (plugin == null) return; plugin.onViewCreated(view); } /** * Returns the stream of custom widgets. */ @NonNull public Stream stream() { return mCustomWidgets.stream(); } /** * Returns the widget id for a specific provider. */ public int getWidgetIdForCustomProvider(@NonNull ComponentName provider) { int index = mWidgetsIdMap.indexOfValue(provider); if (index >= 0) { return LauncherAppWidgetInfo.CUSTOM_WIDGET_ID - mWidgetsIdMap.keyAt(index); } else { return AppWidgetManager.INVALID_APPWIDGET_ID; } } /** * Returns the widget provider in respect to given widget id. */ @Nullable public LauncherAppWidgetProviderInfo getWidgetProvider(int widgetId) { ComponentName cn = mWidgetsIdMap.get(LauncherAppWidgetInfo.CUSTOM_WIDGET_ID - widgetId); for (LauncherAppWidgetProviderInfo info : mCustomWidgets) { if (info.provider.equals(cn)) return info; } return null; } private static CustomAppWidgetProviderInfo newInfo(int providerId, CustomWidgetPlugin plugin, Parcel parcel, Context context) { CustomAppWidgetProviderInfo info = new CustomAppWidgetProviderInfo( parcel, false, providerId); info.provider = new ComponentName( context.getPackageName(), CLS_CUSTOM_WIDGET_PREFIX + providerId); info.label = plugin.getLabel(); info.resizeMode = plugin.getResizeMode(); info.spanX = plugin.getSpanX(); info.spanY = plugin.getSpanY(); info.minSpanX = plugin.getMinSpanX(); info.minSpanY = plugin.getMinSpanY(); return info; } private int findProviderId(CustomWidgetPlugin plugin) { for (int i = 0; i < mPlugins.size(); i++) { int providerId = mPlugins.keyAt(i); if (mPlugins.get(providerId) == plugin) { return providerId; } } return -1; } }