• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.util.Executors.MAIN_EXECUTOR;
22 import static com.android.launcher3.widget.LauncherAppWidgetProviderInfo.CLS_CUSTOM_WIDGET_PREFIX;
23 
24 import android.appwidget.AppWidgetManager;
25 import android.appwidget.AppWidgetProviderInfo;
26 import android.content.ComponentName;
27 import android.content.Context;
28 import android.os.Parcel;
29 import android.os.Process;
30 import android.util.Log;
31 
32 import androidx.annotation.NonNull;
33 import androidx.annotation.Nullable;
34 import androidx.annotation.VisibleForTesting;
35 
36 import com.android.launcher3.R;
37 import com.android.launcher3.dagger.ApplicationContext;
38 import com.android.launcher3.dagger.LauncherAppSingleton;
39 import com.android.launcher3.dagger.LauncherBaseAppComponent;
40 import com.android.launcher3.util.DaggerSingletonObject;
41 import com.android.launcher3.util.DaggerSingletonTracker;
42 import com.android.launcher3.util.PluginManagerWrapper;
43 import com.android.launcher3.util.SafeCloseable;
44 import com.android.launcher3.widget.LauncherAppWidgetHostView;
45 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
46 import com.android.systemui.plugins.CustomWidgetPlugin;
47 import com.android.systemui.plugins.PluginListener;
48 
49 import java.lang.reflect.InvocationTargetException;
50 import java.util.ArrayList;
51 import java.util.HashMap;
52 import java.util.List;
53 import java.util.Map;
54 import java.util.concurrent.CopyOnWriteArrayList;
55 import java.util.stream.Stream;
56 
57 import javax.inject.Inject;
58 
59 /**
60  * CustomWidgetManager handles custom widgets implemented as a plugin.
61  */
62 @LauncherAppSingleton
63 public class CustomWidgetManager implements PluginListener<CustomWidgetPlugin> {
64 
65     public static final DaggerSingletonObject<CustomWidgetManager> INSTANCE =
66             new DaggerSingletonObject<>(LauncherBaseAppComponent::getCustomWidgetManager);
67 
68     private static final String TAG = "CustomWidgetManager";
69     private static final String PLUGIN_PKG = "android";
70     private final Context mContext;
71     private final HashMap<ComponentName, CustomWidgetPlugin> mPlugins;
72     private final List<CustomAppWidgetProviderInfo> mCustomWidgets;
73     private final List<Runnable> mWidgetRefreshCallbacks = new CopyOnWriteArrayList<>();
74     private final @NonNull AppWidgetManager mAppWidgetManager;
75 
76     @Inject
CustomWidgetManager(@pplicationContext Context context, PluginManagerWrapper pluginManager, DaggerSingletonTracker tracker)77     CustomWidgetManager(@ApplicationContext Context context, PluginManagerWrapper pluginManager,
78             DaggerSingletonTracker tracker) {
79         this(context, pluginManager, AppWidgetManager.getInstance(context), tracker);
80     }
81 
82     @VisibleForTesting
CustomWidgetManager(@pplicationContext Context context, PluginManagerWrapper pluginManager, @NonNull AppWidgetManager widgetManager, DaggerSingletonTracker tracker)83     CustomWidgetManager(@ApplicationContext Context context,
84             PluginManagerWrapper pluginManager,
85             @NonNull AppWidgetManager widgetManager,
86             DaggerSingletonTracker tracker) {
87         mContext = context;
88         mAppWidgetManager = widgetManager;
89         mPlugins = new HashMap<>();
90         mCustomWidgets = new ArrayList<>();
91 
92         pluginManager.addPluginListener(this, CustomWidgetPlugin.class, true);
93         if (enableSmartspaceAsAWidget()) {
94             for (String s: context.getResources()
95                     .getStringArray(R.array.custom_widget_providers)) {
96                 try {
97                     Class<?> cls = Class.forName(s);
98                     CustomWidgetPlugin plugin = (CustomWidgetPlugin)
99                             cls.getDeclaredConstructor(Context.class).newInstance(context);
100                     MAIN_EXECUTOR.execute(() -> onPluginConnected(plugin, context));
101                 } catch (ClassNotFoundException | InstantiationException
102                          | IllegalAccessException
103                          | ClassCastException | NoSuchMethodException
104                          | InvocationTargetException e) {
105                     Log.e(TAG, "Exception found when trying to add custom widgets: " + e);
106                 }
107             }
108         }
109         tracker.addCloseable(() -> pluginManager.removePluginListener(this));
110     }
111 
112     @Override
onPluginConnected(CustomWidgetPlugin plugin, Context context)113     public void onPluginConnected(CustomWidgetPlugin plugin, Context context) {
114         CustomAppWidgetProviderInfo info = getAndAddInfo(new ComponentName(
115                 PLUGIN_PKG, CLS_CUSTOM_WIDGET_PREFIX + plugin.getClass().getName()));
116         if (info != null) {
117             plugin.updateWidgetInfo(info, mContext);
118             mPlugins.put(info.provider, plugin);
119             mWidgetRefreshCallbacks.forEach(MAIN_EXECUTOR::execute);
120         }
121     }
122 
123     @Override
onPluginDisconnected(CustomWidgetPlugin plugin)124     public void onPluginDisconnected(CustomWidgetPlugin plugin) {
125         // Leave the providerInfo as plugins can get disconnected/reconnected multiple times
126         mPlugins.values().remove(plugin);
127         mWidgetRefreshCallbacks.forEach(MAIN_EXECUTOR::execute);
128     }
129 
130     @VisibleForTesting
131     @NonNull
getPlugins()132     Map<ComponentName, CustomWidgetPlugin> getPlugins() {
133         return mPlugins;
134     }
135 
136     /**
137      * Inject a callback function to refresh the widgets.
138      * @return a closeable to remove this callback
139      */
addWidgetRefreshCallback(Runnable callback)140     public SafeCloseable addWidgetRefreshCallback(Runnable callback) {
141         mWidgetRefreshCallbacks.add(callback);
142         return () -> mWidgetRefreshCallbacks.remove(callback);
143     }
144 
145     /**
146      * Callback method to inform a plugin it's corresponding widget has been created.
147      */
onViewCreated(LauncherAppWidgetHostView view)148     public void onViewCreated(LauncherAppWidgetHostView view) {
149         CustomAppWidgetProviderInfo info = (CustomAppWidgetProviderInfo) view.getAppWidgetInfo();
150         CustomWidgetPlugin plugin = mPlugins.get(info.provider);
151         if (plugin != null) {
152             plugin.onViewCreated(view);
153         }
154     }
155 
156     /**
157      * Returns the stream of custom widgets.
158      */
159     @NonNull
stream()160     public Stream<CustomAppWidgetProviderInfo> stream() {
161         return mCustomWidgets.stream();
162     }
163 
164     /**
165      * Returns the widget provider in respect to given widget id.
166      */
167     @Nullable
getWidgetProvider(ComponentName cn)168     public LauncherAppWidgetProviderInfo getWidgetProvider(ComponentName cn) {
169         LauncherAppWidgetProviderInfo info = mCustomWidgets.stream()
170                 .filter(w -> w.getComponent().equals(cn)).findAny().orElse(null);
171         if (info == null) {
172             // If the info is not present, add a placeholder info since the
173             // plugin might get loaded later
174             info = getAndAddInfo(cn);
175         }
176         return info;
177     }
178 
179     /**
180      * Returns an id to set as the appWidgetId for a custom widget.
181      */
allocateCustomAppWidgetId(ComponentName componentName)182     public int allocateCustomAppWidgetId(ComponentName componentName) {
183         return CUSTOM_WIDGET_ID - mCustomWidgets.indexOf(getWidgetProvider(componentName));
184     }
185 
186     @Nullable
getAndAddInfo(ComponentName cn)187     private CustomAppWidgetProviderInfo getAndAddInfo(ComponentName cn) {
188         for (CustomAppWidgetProviderInfo info : mCustomWidgets) {
189             if (info.provider.equals(cn)) return info;
190         }
191 
192         List<AppWidgetProviderInfo> providers = mAppWidgetManager
193                 .getInstalledProvidersForProfile(Process.myUserHandle());
194         if (providers.isEmpty()) return null;
195         Parcel parcel = Parcel.obtain();
196         providers.get(0).writeToParcel(parcel, 0);
197         parcel.setDataPosition(0);
198         CustomAppWidgetProviderInfo info = new CustomAppWidgetProviderInfo(parcel, false);
199         parcel.recycle();
200 
201         info.provider = cn;
202         info.initialLayout = 0;
203         mCustomWidgets.add(info);
204         return info;
205     }
206 }
207