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.Flags.enableSmartspaceAsAWidget;
20 import static com.android.launcher3.model.data.LauncherAppWidgetInfo.CUSTOM_WIDGET_ID;
21 import static com.android.launcher3.widget.LauncherAppWidgetProviderInfo.CLS_CUSTOM_WIDGET_PREFIX;
22 
23 import android.appwidget.AppWidgetManager;
24 import android.appwidget.AppWidgetProviderInfo;
25 import android.content.ComponentName;
26 import android.content.Context;
27 import android.os.Parcel;
28 import android.os.Process;
29 import android.util.Log;
30 
31 import androidx.annotation.NonNull;
32 import androidx.annotation.Nullable;
33 
34 import com.android.launcher3.R;
35 import com.android.launcher3.util.MainThreadInitializedObject;
36 import com.android.launcher3.util.PackageUserKey;
37 import com.android.launcher3.util.PluginManagerWrapper;
38 import com.android.launcher3.util.SafeCloseable;
39 import com.android.launcher3.widget.LauncherAppWidgetHostView;
40 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
41 import com.android.systemui.plugins.CustomWidgetPlugin;
42 import com.android.systemui.plugins.PluginListener;
43 
44 import java.lang.reflect.InvocationTargetException;
45 import java.util.ArrayList;
46 import java.util.HashMap;
47 import java.util.List;
48 import java.util.function.Consumer;
49 import java.util.stream.Stream;
50 
51 /**
52  * CustomWidgetManager handles custom widgets implemented as a plugin.
53  */
54 public class CustomWidgetManager implements PluginListener<CustomWidgetPlugin>, SafeCloseable {
55 
56     public static final MainThreadInitializedObject<CustomWidgetManager> INSTANCE =
57             new MainThreadInitializedObject<>(CustomWidgetManager::new);
58 
59     private static final String TAG = "CustomWidgetManager";
60     private static final String PLUGIN_PKG = "android";
61     private final Context mContext;
62     private final HashMap<ComponentName, CustomWidgetPlugin> mPlugins;
63     private final List<CustomAppWidgetProviderInfo> mCustomWidgets;
64     private Consumer<PackageUserKey> mWidgetRefreshCallback;
65 
CustomWidgetManager(Context context)66     private CustomWidgetManager(Context context) {
67         mContext = context;
68         mPlugins = new HashMap<>();
69         mCustomWidgets = new ArrayList<>();
70         PluginManagerWrapper.INSTANCE.get(context)
71                 .addPluginListener(this, CustomWidgetPlugin.class, true);
72 
73         if (enableSmartspaceAsAWidget()) {
74             for (String s: context.getResources()
75                     .getStringArray(R.array.custom_widget_providers)) {
76                 try {
77                     Class<?> cls = Class.forName(s);
78                     CustomWidgetPlugin plugin = (CustomWidgetPlugin)
79                             cls.getDeclaredConstructor(Context.class).newInstance(context);
80                     onPluginConnected(plugin, context);
81                 } catch (ClassNotFoundException | InstantiationException | IllegalAccessException
82                          | ClassCastException | NoSuchMethodException
83                          | InvocationTargetException e) {
84                     Log.e(TAG, "Exception found when trying to add custom widgets: " + e);
85                 }
86             }
87         }
88     }
89 
90     @Override
close()91     public void close() {
92         PluginManagerWrapper.INSTANCE.get(mContext).removePluginListener(this);
93     }
94 
95     @Override
onPluginConnected(CustomWidgetPlugin plugin, Context context)96     public void onPluginConnected(CustomWidgetPlugin plugin, Context context) {
97         List<AppWidgetProviderInfo> providers = AppWidgetManager.getInstance(context)
98                 .getInstalledProvidersForProfile(Process.myUserHandle());
99         if (providers.isEmpty()) return;
100         Parcel parcel = Parcel.obtain();
101         providers.get(0).writeToParcel(parcel, 0);
102         parcel.setDataPosition(0);
103         CustomAppWidgetProviderInfo info = newInfo(plugin, parcel);
104         parcel.recycle();
105         mPlugins.put(info.provider, plugin);
106         mCustomWidgets.add(info);
107     }
108 
109     @Override
onPluginDisconnected(CustomWidgetPlugin plugin)110     public void onPluginDisconnected(CustomWidgetPlugin plugin) {
111         ComponentName cn = getWidgetProviderComponent(plugin);
112         mPlugins.remove(cn);
113         mCustomWidgets.removeIf(w -> w.getComponent().equals(cn));
114     }
115 
116     /**
117      * Inject a callback function to refresh the widgets.
118      */
setWidgetRefreshCallback(Consumer<PackageUserKey> cb)119     public void setWidgetRefreshCallback(Consumer<PackageUserKey> cb) {
120         mWidgetRefreshCallback = cb;
121     }
122 
123     /**
124      * Callback method to inform a plugin it's corresponding widget has been created.
125      */
onViewCreated(LauncherAppWidgetHostView view)126     public void onViewCreated(LauncherAppWidgetHostView view) {
127         CustomAppWidgetProviderInfo info = (CustomAppWidgetProviderInfo) view.getAppWidgetInfo();
128         CustomWidgetPlugin plugin = mPlugins.get(info.provider);
129         if (plugin == null) return;
130         plugin.onViewCreated(view);
131     }
132 
133     /**
134      * Returns the stream of custom widgets.
135      */
136     @NonNull
stream()137     public Stream<CustomAppWidgetProviderInfo> stream() {
138         return mCustomWidgets.stream();
139     }
140 
141     /**
142      * Returns the widget provider in respect to given widget id.
143      */
144     @Nullable
getWidgetProvider(ComponentName cn)145     public LauncherAppWidgetProviderInfo getWidgetProvider(ComponentName cn) {
146         return mCustomWidgets.stream()
147                 .filter(w -> w.getComponent().equals(cn)).findAny().orElse(null);
148     }
149 
newInfo(CustomWidgetPlugin plugin, Parcel parcel)150     private CustomAppWidgetProviderInfo newInfo(CustomWidgetPlugin plugin, Parcel parcel) {
151         CustomAppWidgetProviderInfo info = new CustomAppWidgetProviderInfo(parcel, false);
152         info.provider = getWidgetProviderComponent(plugin);
153         plugin.updateWidgetInfo(info, mContext);
154         return info;
155     }
156 
157     /**
158      * Returns an id to set as the appWidgetId for a custom widget.
159      */
allocateCustomAppWidgetId(ComponentName componentName)160     public int allocateCustomAppWidgetId(ComponentName componentName) {
161         return CUSTOM_WIDGET_ID - mCustomWidgets.indexOf(getWidgetProvider(componentName));
162     }
163 
getWidgetProviderComponent(CustomWidgetPlugin plugin)164     private ComponentName getWidgetProviderComponent(CustomWidgetPlugin plugin) {
165         return new ComponentName(
166                 PLUGIN_PKG, CLS_CUSTOM_WIDGET_PREFIX + plugin.getClass().getName());
167     }
168 }
169