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