• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (C) 2022 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 package com.android.launcher3.uioverrides;
17 
18 import static com.android.launcher3.BuildConfig.WIDGETS_ENABLED;
19 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
20 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
21 
22 import android.appwidget.AppWidgetHost;
23 import android.appwidget.AppWidgetHostView;
24 import android.appwidget.AppWidgetProviderInfo;
25 import android.content.Context;
26 import android.util.Log;
27 import android.util.SparseArray;
28 import android.widget.RemoteViews;
29 
30 import androidx.annotation.AnyThread;
31 import androidx.annotation.NonNull;
32 import androidx.annotation.Nullable;
33 import androidx.annotation.UiThread;
34 
35 import com.android.launcher3.config.FeatureFlags;
36 import com.android.launcher3.util.IntSet;
37 import com.android.launcher3.util.SafeCloseable;
38 import com.android.launcher3.widget.LauncherAppWidgetHostView;
39 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
40 import com.android.launcher3.widget.LauncherWidgetHolder;
41 
42 import java.util.ArrayList;
43 import java.util.Collections;
44 import java.util.List;
45 import java.util.Set;
46 import java.util.WeakHashMap;
47 import java.util.function.BiConsumer;
48 import java.util.function.IntConsumer;
49 
50 /**
51  * {@link LauncherWidgetHolder} that puts the app widget host in the background
52  */
53 public final class QuickstepWidgetHolder extends LauncherWidgetHolder {
54 
55     private static final String TAG = "QuickstepWidgetHolder";
56 
57     private static final UpdateKey<AppWidgetProviderInfo> KEY_PROVIDER_UPDATE =
58             AppWidgetHostView::onUpdateProviderInfo;
59     private static final UpdateKey<RemoteViews> KEY_VIEWS_UPDATE =
60             AppWidgetHostView::updateAppWidget;
61     private static final UpdateKey<Integer> KEY_VIEW_DATA_CHANGED =
62             AppWidgetHostView::onViewDataChanged;
63 
64     private static final List<QuickstepWidgetHolder> sHolders = new ArrayList<>();
65     private static final SparseArray<QuickstepWidgetHolderListener> sListeners =
66             new SparseArray<>();
67 
68     private static AppWidgetHost sWidgetHost = null;
69 
70     private final UpdateHandler mUpdateHandler = this::onWidgetUpdate;
71     private final @Nullable RemoteViews.InteractionHandler mInteractionHandler;
72 
73     private final @NonNull IntConsumer mAppWidgetRemovedCallback;
74 
75     // Map to all pending updated keyed with appWidgetId;
76     private final SparseArray<PendingUpdate> mPendingUpdateMap = new SparseArray<>();
77 
QuickstepWidgetHolder(@onNull Context context, @Nullable IntConsumer appWidgetRemovedCallback, @Nullable RemoteViews.InteractionHandler interactionHandler)78     private QuickstepWidgetHolder(@NonNull Context context,
79             @Nullable IntConsumer appWidgetRemovedCallback,
80             @Nullable RemoteViews.InteractionHandler interactionHandler) {
81         super(context, appWidgetRemovedCallback);
82         mAppWidgetRemovedCallback = appWidgetRemovedCallback != null ? appWidgetRemovedCallback
83                 : i -> {};
84         mInteractionHandler = interactionHandler;
85         MAIN_EXECUTOR.execute(() -> sHolders.add(this));
86     }
87 
88     @Override
89     @NonNull
createHost(@onNull Context context, @Nullable IntConsumer appWidgetRemovedCallback)90     protected AppWidgetHost createHost(@NonNull Context context,
91             @Nullable IntConsumer appWidgetRemovedCallback) {
92         if (sWidgetHost == null) {
93             sWidgetHost = new QuickstepAppWidgetHost(context.getApplicationContext(),
94                     i -> MAIN_EXECUTOR.execute(() ->
95                             sHolders.forEach(h -> h.mAppWidgetRemovedCallback.accept(i))),
96                     () -> MAIN_EXECUTOR.execute(() ->
97                             sHolders.forEach(h ->
98                                     // Listeners might remove themselves from the list during the
99                                     // iteration. Creating a copy of the list to avoid exceptions
100                                     // for concurrent modification.
101                                     new ArrayList<>(h.mProviderChangedListeners).forEach(
102                                     ProviderChangedListener::notifyWidgetProvidersChanged))),
103                     UI_HELPER_EXECUTOR.getLooper());
104             if (WIDGETS_ENABLED) {
105                 sWidgetHost.startListening();
106             }
107         }
108         return sWidgetHost;
109     }
110 
111     @Override
updateDeferredView()112     protected void updateDeferredView() {
113         int count = mPendingUpdateMap.size();
114         for (int i = 0; i < count; i++) {
115             int widgetId = mPendingUpdateMap.keyAt(i);
116             AppWidgetHostView view = mViews.get(widgetId);
117             PendingUpdate pendingUpdate = mPendingUpdateMap.valueAt(i);
118             if (view == null || pendingUpdate == null) {
119                 continue;
120             }
121             if (pendingUpdate.providerInfo != null) {
122                 KEY_PROVIDER_UPDATE.accept(view, pendingUpdate.providerInfo);
123             }
124             if (pendingUpdate.remoteViews != null) {
125                 KEY_VIEWS_UPDATE.accept(view, pendingUpdate.remoteViews);
126             }
127             pendingUpdate.changedViews.forEach(
128                     viewId -> KEY_VIEW_DATA_CHANGED.accept(view, viewId));
129         }
130         mPendingUpdateMap.clear();
131     }
132 
onWidgetUpdate(int widgetId, UpdateKey<T> key, T data)133     private <T> void onWidgetUpdate(int widgetId, UpdateKey<T> key, T data) {
134         if (isListening()) {
135             AppWidgetHostView view = mViews.get(widgetId);
136             if (view == null) {
137                 return;
138             }
139             key.accept(view, data);
140             return;
141         }
142 
143         PendingUpdate pendingUpdate = mPendingUpdateMap.get(widgetId);
144         if (pendingUpdate == null) {
145             pendingUpdate = new PendingUpdate();
146             mPendingUpdateMap.put(widgetId, pendingUpdate);
147         }
148 
149         if (KEY_PROVIDER_UPDATE.equals(key)) {
150             // For provider change, remove all updates
151             pendingUpdate.providerInfo = (AppWidgetProviderInfo) data;
152             pendingUpdate.remoteViews = null;
153             pendingUpdate.changedViews.clear();
154         } else if (KEY_VIEWS_UPDATE.equals(key)) {
155             // For views update, remove all previous updates, except the provider
156             pendingUpdate.remoteViews = (RemoteViews) data;
157         } else if (KEY_VIEW_DATA_CHANGED.equals(key)) {
158             pendingUpdate.changedViews.add((Integer) data);
159         }
160     }
161 
162     /**
163      * Delete the specified app widget from the host
164      * @param appWidgetId The ID of the app widget to be deleted
165      */
166     @Override
deleteAppWidgetId(int appWidgetId)167     public void deleteAppWidgetId(int appWidgetId) {
168         super.deleteAppWidgetId(appWidgetId);
169         sListeners.remove(appWidgetId);
170     }
171 
172     /**
173      * Called when the launcher is destroyed
174      */
175     @Override
destroy()176     public void destroy() {
177         try {
178             MAIN_EXECUTOR.submit(() -> {
179                 clearViews();
180                 sHolders.remove(this);
181             }).get();
182         } catch (Exception e) {
183             Log.e(TAG, "Failed to remove self from holder list", e);
184         }
185     }
186 
187     @Override
shouldListen(int flags)188     protected boolean shouldListen(int flags) {
189         return (flags & (FLAG_STATE_IS_NORMAL | FLAG_ACTIVITY_STARTED))
190                 == (FLAG_STATE_IS_NORMAL | FLAG_ACTIVITY_STARTED);
191     }
192 
193     /**
194      * Stop the host from updating the widget views
195      */
196     @Override
stopListening()197     public void stopListening() {
198         if (!WIDGETS_ENABLED) {
199             return;
200         }
201 
202         sWidgetHost.setAppWidgetHidden();
203         setListeningFlag(false);
204     }
205 
206     @Override
addOnUpdateListener(int appWidgetId, LauncherAppWidgetProviderInfo appWidget, Runnable callback)207     public SafeCloseable addOnUpdateListener(int appWidgetId,
208             LauncherAppWidgetProviderInfo appWidget, Runnable callback) {
209         UpdateHandler handler = new UpdateHandler() {
210             @Override
211             public <T> void onWidgetUpdate(int widgetId, UpdateKey<T> key, T data) {
212                 if (KEY_VIEWS_UPDATE == key) {
213                     callback.run();
214                 }
215             }
216         };
217         QuickstepWidgetHolderListener holderListener = getHolderListener(appWidgetId);
218         holderListener.addHolder(handler);
219         return () -> holderListener.mListeningHolders.remove(handler);
220     }
221 
222     /**
223      * Recycling logic:
224      * The holder doesn't maintain any states associated with the view, so if the view was
225      * initially initialized by this holder, all its state are already set in the view. We just
226      * update the RemoteViews for this view again, in case the widget sent an update during the
227      * time between inflation and recycle.
228      */
229     @Override
recycleExistingView(LauncherAppWidgetHostView view)230     protected LauncherAppWidgetHostView recycleExistingView(LauncherAppWidgetHostView view) {
231         RemoteViews views = getHolderListener(view.getAppWidgetId()).addHolder(mUpdateHandler);
232         view.updateAppWidget(views);
233         return view;
234     }
235 
236     @NonNull
237     @Override
createViewInternal( int appWidgetId, @NonNull LauncherAppWidgetProviderInfo appWidget)238     protected LauncherAppWidgetHostView createViewInternal(
239             int appWidgetId, @NonNull LauncherAppWidgetProviderInfo appWidget) {
240         LauncherAppWidgetHostView widgetView = new LauncherAppWidgetHostView(mContext);
241         widgetView.setInteractionHandler(mInteractionHandler);
242         widgetView.setAppWidget(appWidgetId, appWidget);
243         widgetView.updateAppWidget(getHolderListener(appWidgetId).addHolder(mUpdateHandler));
244         return widgetView;
245     }
246 
getHolderListener(int appWidgetId)247     private static QuickstepWidgetHolderListener getHolderListener(int appWidgetId) {
248         QuickstepWidgetHolderListener listener = sListeners.get(appWidgetId);
249         if (listener == null) {
250             listener = new QuickstepWidgetHolderListener(appWidgetId);
251             sWidgetHost.setListener(appWidgetId, listener);
252             sListeners.put(appWidgetId, listener);
253         }
254         return listener;
255     }
256 
257     /**
258      * Clears all the views from the host
259      */
260     @Override
clearViews()261     public void clearViews() {
262         mViews.clear();
263         for (int i = sListeners.size() - 1; i >= 0; i--) {
264             sListeners.valueAt(i).mListeningHolders.remove(mUpdateHandler);
265         }
266     }
267 
268     /**
269      * Clears all the internal widget views excluding the update listeners
270      */
271     @Override
clearWidgetViews()272     public void clearWidgetViews() {
273         mViews.clear();
274     }
275 
276     private static class QuickstepWidgetHolderListener
277             implements AppWidgetHost.AppWidgetHostListener {
278 
279         // Static listeners should use a set that is backed by WeakHashMap to avoid memory leak
280         private final Set<UpdateHandler> mListeningHolders = Collections.newSetFromMap(
281                 new WeakHashMap<>());
282 
283         private final int mWidgetId;
284 
285         private @Nullable RemoteViews mRemoteViews;
286 
QuickstepWidgetHolderListener(int widgetId)287         QuickstepWidgetHolderListener(int widgetId) {
288             mWidgetId = widgetId;
289         }
290 
291         @UiThread
292         @Nullable
addHolder(@onNull UpdateHandler holder)293         public RemoteViews addHolder(@NonNull UpdateHandler holder) {
294             mListeningHolders.add(holder);
295             return mRemoteViews;
296         }
297 
298         @Override
299         @AnyThread
onUpdateProviderInfo(@ullable AppWidgetProviderInfo info)300         public void onUpdateProviderInfo(@Nullable AppWidgetProviderInfo info) {
301             mRemoteViews = null;
302             executeOnMainExecutor(KEY_PROVIDER_UPDATE, info);
303         }
304 
305         @Override
306         @AnyThread
updateAppWidget(@ullable RemoteViews views)307         public void updateAppWidget(@Nullable RemoteViews views) {
308             mRemoteViews = views;
309             executeOnMainExecutor(KEY_VIEWS_UPDATE, mRemoteViews);
310         }
311 
312         @Override
313         @AnyThread
onViewDataChanged(int viewId)314         public void onViewDataChanged(int viewId) {
315             executeOnMainExecutor(KEY_VIEW_DATA_CHANGED, viewId);
316         }
317 
executeOnMainExecutor(UpdateKey<T> key, T data)318         private <T> void executeOnMainExecutor(UpdateKey<T> key, T data) {
319             MAIN_EXECUTOR.execute(() -> mListeningHolders.forEach(holder ->
320                     holder.onWidgetUpdate(mWidgetId, key, data)));
321         }
322     }
323 
324     /**
325      * {@code HolderFactory} subclass that takes an interaction handler as one of the parameters
326      * when creating a new instance.
327      */
328     public static class QuickstepHolderFactory extends HolderFactory {
329 
330         @SuppressWarnings("unused")
QuickstepHolderFactory(Context context)331         public QuickstepHolderFactory(Context context) { }
332 
333         @Override
newInstance(@onNull Context context, @Nullable IntConsumer appWidgetRemovedCallback)334         public LauncherWidgetHolder newInstance(@NonNull Context context,
335                 @Nullable IntConsumer appWidgetRemovedCallback) {
336             return newInstance(context, appWidgetRemovedCallback, null);
337         }
338 
339         /**
340          * @param context The context of the caller
341          * @param appWidgetRemovedCallback The callback that is called when widgets are removed
342          * @param interactionHandler The interaction handler when the widgets are clicked
343          * @return A new {@link LauncherWidgetHolder} instance
344          */
newInstance(@onNull Context context, @Nullable IntConsumer appWidgetRemovedCallback, @Nullable RemoteViews.InteractionHandler interactionHandler)345         public LauncherWidgetHolder newInstance(@NonNull Context context,
346                 @Nullable IntConsumer appWidgetRemovedCallback,
347                 @Nullable RemoteViews.InteractionHandler interactionHandler) {
348 
349             if (!FeatureFlags.ENABLE_WIDGET_HOST_IN_BACKGROUND.get()) {
350                 return new LauncherWidgetHolder(context, appWidgetRemovedCallback) {
351                     @Override
352                     protected AppWidgetHost createHost(Context context,
353                             @Nullable IntConsumer appWidgetRemovedCallback) {
354                         AppWidgetHost host = super.createHost(context, appWidgetRemovedCallback);
355                         host.setInteractionHandler(interactionHandler);
356                         return host;
357                     }
358                 };
359             }
360             return new QuickstepWidgetHolder(context, appWidgetRemovedCallback, interactionHandler);
361         }
362     }
363 
364     private interface UpdateKey<T> extends BiConsumer<AppWidgetHostView, T> { }
365 
366     private interface UpdateHandler {
367         <T> void onWidgetUpdate(int widgetId, UpdateKey<T> key, T data);
368     }
369 
370     private static class PendingUpdate {
371         public final IntSet changedViews = new IntSet();
372         public AppWidgetProviderInfo providerInfo;
373         public RemoteViews remoteViews;
374     }
375 }
376