1 /* 2 * Copyright (C) 2009 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; 18 19 import static com.android.launcher3.widget.LauncherWidgetHolder.APPWIDGET_HOST_ID; 20 21 import android.appwidget.AppWidgetHost; 22 import android.appwidget.AppWidgetProviderInfo; 23 import android.content.Context; 24 import android.view.accessibility.AccessibilityNodeInfo; 25 import android.widget.RemoteViews; 26 27 import androidx.annotation.NonNull; 28 import androidx.annotation.Nullable; 29 30 import com.android.launcher3.LauncherAppState; 31 import com.android.launcher3.util.Executors; 32 import com.android.launcher3.util.SafeCloseable; 33 import com.android.launcher3.widget.LauncherWidgetHolder.ProviderChangedListener; 34 35 import java.util.ArrayList; 36 import java.util.Collections; 37 import java.util.List; 38 import java.util.Set; 39 import java.util.WeakHashMap; 40 import java.util.function.IntConsumer; 41 42 /** 43 * Specific {@link AppWidgetHost} that creates our {@link LauncherAppWidgetHostView} 44 * which correctly captures all long-press events. This ensures that users can 45 * always pick up and move widgets. 46 */ 47 class LauncherAppWidgetHost extends AppWidgetHost { 48 @NonNull 49 private final List<ProviderChangedListener> mProviderChangeListeners; 50 51 @NonNull 52 private final Context mContext; 53 54 @Nullable 55 private final IntConsumer mAppWidgetRemovedCallback; 56 57 @Nullable 58 private ListenableHostView mViewToRecycle; 59 LauncherAppWidgetHost(@onNull Context context, @Nullable IntConsumer appWidgetRemovedCallback, List<ProviderChangedListener> providerChangeListeners)60 public LauncherAppWidgetHost(@NonNull Context context, 61 @Nullable IntConsumer appWidgetRemovedCallback, 62 List<ProviderChangedListener> providerChangeListeners) { 63 super(context, APPWIDGET_HOST_ID); 64 mContext = context; 65 mAppWidgetRemovedCallback = appWidgetRemovedCallback; 66 mProviderChangeListeners = providerChangeListeners; 67 } 68 69 @Override onProvidersChanged()70 protected void onProvidersChanged() { 71 if (!mProviderChangeListeners.isEmpty()) { 72 for (LauncherWidgetHolder.ProviderChangedListener callback : 73 new ArrayList<>(mProviderChangeListeners)) { 74 callback.notifyWidgetProvidersChanged(); 75 } 76 } 77 } 78 79 /** 80 * Sets the view to be recycled for the next widget creation. 81 */ recycleViewForNextCreation(ListenableHostView viewToRecycle)82 public void recycleViewForNextCreation(ListenableHostView viewToRecycle) { 83 mViewToRecycle = viewToRecycle; 84 } 85 86 @Override 87 @NonNull onCreateView(Context context, int appWidgetId, AppWidgetProviderInfo appWidget)88 public LauncherAppWidgetHostView onCreateView(Context context, int appWidgetId, 89 AppWidgetProviderInfo appWidget) { 90 ListenableHostView result = 91 mViewToRecycle != null ? mViewToRecycle : new ListenableHostView(context); 92 mViewToRecycle = null; 93 return result; 94 } 95 96 /** 97 * Called when the AppWidget provider for a AppWidget has been upgraded to a new apk. 98 */ 99 @Override onProviderChanged(int appWidgetId, @NonNull AppWidgetProviderInfo appWidget)100 protected void onProviderChanged(int appWidgetId, @NonNull AppWidgetProviderInfo appWidget) { 101 LauncherAppWidgetProviderInfo info = LauncherAppWidgetProviderInfo.fromProviderInfo( 102 mContext, appWidget); 103 super.onProviderChanged(appWidgetId, info); 104 // The super method updates the dimensions of the providerInfo. Update the 105 // launcher spans accordingly. 106 info.initSpans(mContext, LauncherAppState.getIDP(mContext)); 107 } 108 109 /** 110 * Called on an appWidget is removed for a widgetId 111 * 112 * @param appWidgetId TODO: make this override when SDK is updated 113 */ 114 @Override onAppWidgetRemoved(int appWidgetId)115 public void onAppWidgetRemoved(int appWidgetId) { 116 if (mAppWidgetRemovedCallback == null) { 117 return; 118 } 119 // Route the call via model thread, in case it comes while a loader-bind is in progress 120 Executors.MODEL_EXECUTOR.execute( 121 () -> Executors.MAIN_EXECUTOR.execute( 122 () -> mAppWidgetRemovedCallback.accept(appWidgetId))); 123 } 124 125 /** 126 * The same as super.clearViews(), except with the scope exposed 127 */ 128 @Override clearViews()129 public void clearViews() { 130 super.clearViews(); 131 } 132 133 public static class ListenableHostView extends LauncherAppWidgetHostView { 134 135 private Set<Runnable> mUpdateListeners = Collections.EMPTY_SET; 136 ListenableHostView(Context context)137 ListenableHostView(Context context) { 138 super(context); 139 } 140 141 @Override updateAppWidget(RemoteViews remoteViews)142 public void updateAppWidget(RemoteViews remoteViews) { 143 super.updateAppWidget(remoteViews); 144 mUpdateListeners.forEach(Runnable::run); 145 } 146 147 @Override onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)148 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 149 super.onInitializeAccessibilityNodeInfo(info); 150 info.setClassName(LauncherAppWidgetHostView.class.getName()); 151 } 152 153 /** 154 * Adds a callback to be run everytime the provided app widget updates. 155 * @return a closable to remove this callback 156 */ addUpdateListener(Runnable callback)157 public SafeCloseable addUpdateListener(Runnable callback) { 158 if (mUpdateListeners == Collections.EMPTY_SET) { 159 mUpdateListeners = Collections.newSetFromMap(new WeakHashMap<>()); 160 } 161 mUpdateListeners.add(callback); 162 return () -> mUpdateListeners.remove(callback); 163 } 164 } 165 } 166