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