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