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