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 17package com.android.launcher3; 18 19import static android.app.Activity.RESULT_CANCELED; 20 21import android.appwidget.AppWidgetHost; 22import android.appwidget.AppWidgetHostView; 23import android.appwidget.AppWidgetManager; 24import android.appwidget.AppWidgetProviderInfo; 25import android.content.ActivityNotFoundException; 26import android.content.Context; 27import android.content.Intent; 28import android.os.Handler; 29import android.util.Log; 30import android.util.SparseArray; 31import android.view.LayoutInflater; 32import android.widget.Toast; 33 34import com.android.launcher3.config.FeatureFlags; 35import com.android.launcher3.widget.DeferredAppWidgetHostView; 36import com.android.launcher3.widget.LauncherAppWidgetHostView; 37 38import java.util.ArrayList; 39 40 41/** 42 * Specific {@link AppWidgetHost} that creates our {@link LauncherAppWidgetHostView} 43 * which correctly captures all long-press events. This ensures that users can 44 * always pick up and move widgets. 45 */ 46public class LauncherAppWidgetHost extends AppWidgetHost { 47 48 private static final int FLAG_LISTENING = 1; 49 private static final int FLAG_RESUMED = 1 << 1; 50 private static final int FLAG_LISTEN_IF_RESUMED = 1 << 2; 51 52 public static final int APPWIDGET_HOST_ID = 1024; 53 54 private final ArrayList<ProviderChangedListener> mProviderChangeListeners = new ArrayList<>(); 55 private final SparseArray<LauncherAppWidgetHostView> mViews = new SparseArray<>(); 56 57 private final Context mContext; 58 private int mFlags = FLAG_RESUMED; 59 60 public LauncherAppWidgetHost(Context context) { 61 super(context, APPWIDGET_HOST_ID); 62 mContext = context; 63 } 64 65 @Override 66 protected LauncherAppWidgetHostView onCreateView(Context context, int appWidgetId, 67 AppWidgetProviderInfo appWidget) { 68 LauncherAppWidgetHostView view = new LauncherAppWidgetHostView(context); 69 mViews.put(appWidgetId, view); 70 return view; 71 } 72 73 @Override 74 public void startListening() { 75 if (FeatureFlags.GO_DISABLE_WIDGETS) { 76 return; 77 } 78 mFlags |= FLAG_LISTENING; 79 try { 80 super.startListening(); 81 } catch (Exception e) { 82 if (!Utilities.isBinderSizeError(e)) { 83 throw new RuntimeException(e); 84 } 85 // We're willing to let this slide. The exception is being caused by the list of 86 // RemoteViews which is being passed back. The startListening relationship will 87 // have been established by this point, and we will end up populating the 88 // widgets upon bind anyway. See issue 14255011 for more context. 89 } 90 91 // We go in reverse order and inflate any deferred widget 92 for (int i = mViews.size() - 1; i >= 0; i--) { 93 LauncherAppWidgetHostView view = mViews.valueAt(i); 94 if (view instanceof DeferredAppWidgetHostView) { 95 view.reInflate(); 96 } 97 } 98 } 99 100 @Override 101 public void stopListening() { 102 if (FeatureFlags.GO_DISABLE_WIDGETS) { 103 return; 104 } 105 mFlags &= ~FLAG_LISTENING; 106 super.stopListening(); 107 } 108 109 /** 110 * Updates the resumed state of the host. 111 * When a host is not resumed, it defers calls to startListening until host is resumed again. 112 * But if the host was already listening, it will not call stopListening. 113 * 114 * @see #setListenIfResumed(boolean) 115 */ 116 public void setResumed(boolean isResumed) { 117 if (isResumed == ((mFlags & FLAG_RESUMED) != 0)) { 118 return; 119 } 120 if (isResumed) { 121 mFlags |= FLAG_RESUMED; 122 // Start listening if we were supposed to start listening on resume 123 if ((mFlags & FLAG_LISTEN_IF_RESUMED) != 0 && (mFlags & FLAG_LISTENING) == 0) { 124 startListening(); 125 } 126 } else { 127 mFlags &= ~FLAG_RESUMED; 128 } 129 } 130 131 /** 132 * Updates the listening state of the host. If the host is not resumed, startListening is 133 * deferred until next resume. 134 * 135 * @see #setResumed(boolean) 136 */ 137 public void setListenIfResumed(boolean listenIfResumed) { 138 if (!Utilities.ATLEAST_NOUGAT_MR1) { 139 return; 140 } 141 if (listenIfResumed == ((mFlags & FLAG_LISTEN_IF_RESUMED) != 0)) { 142 return; 143 } 144 if (listenIfResumed) { 145 mFlags |= FLAG_LISTEN_IF_RESUMED; 146 if ((mFlags & FLAG_RESUMED) != 0) { 147 // If we are resumed, start listening immediately. Note we do not check for 148 // duplicate calls before calling startListening as startListening is safe to call 149 // multiple times. 150 startListening(); 151 } 152 } else { 153 mFlags &= ~FLAG_LISTEN_IF_RESUMED; 154 stopListening(); 155 } 156 } 157 158 @Override 159 public int allocateAppWidgetId() { 160 if (FeatureFlags.GO_DISABLE_WIDGETS) { 161 return AppWidgetManager.INVALID_APPWIDGET_ID; 162 } 163 164 return super.allocateAppWidgetId(); 165 } 166 167 public void addProviderChangeListener(ProviderChangedListener callback) { 168 mProviderChangeListeners.add(callback); 169 } 170 171 public void removeProviderChangeListener(ProviderChangedListener callback) { 172 mProviderChangeListeners.remove(callback); 173 } 174 175 protected void onProvidersChanged() { 176 if (!mProviderChangeListeners.isEmpty()) { 177 for (ProviderChangedListener callback : new ArrayList<>(mProviderChangeListeners)) { 178 callback.notifyWidgetProvidersChanged(); 179 } 180 } 181 } 182 183 public AppWidgetHostView createView(Context context, int appWidgetId, 184 LauncherAppWidgetProviderInfo appWidget) { 185 if (appWidget.isCustomWidget()) { 186 LauncherAppWidgetHostView lahv = new LauncherAppWidgetHostView(context); 187 LayoutInflater inflater = (LayoutInflater) 188 context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 189 inflater.inflate(appWidget.initialLayout, lahv); 190 lahv.setAppWidget(0, appWidget); 191 return lahv; 192 } else if ((mFlags & FLAG_LISTENING) == 0) { 193 DeferredAppWidgetHostView view = new DeferredAppWidgetHostView(context); 194 view.setAppWidget(appWidgetId, appWidget); 195 mViews.put(appWidgetId, view); 196 return view; 197 } else { 198 try { 199 return super.createView(context, appWidgetId, appWidget); 200 } catch (Exception e) { 201 if (!Utilities.isBinderSizeError(e)) { 202 throw new RuntimeException(e); 203 } 204 205 // If the exception was thrown while fetching the remote views, let the view stay. 206 // This will ensure that if the widget posts a valid update later, the view 207 // will update. 208 LauncherAppWidgetHostView view = mViews.get(appWidgetId); 209 if (view == null) { 210 view = onCreateView(mContext, appWidgetId, appWidget); 211 } 212 view.setAppWidget(appWidgetId, appWidget); 213 view.switchToErrorView(); 214 return view; 215 } 216 } 217 } 218 219 /** 220 * Called when the AppWidget provider for a AppWidget has been upgraded to a new apk. 221 */ 222 @Override 223 protected void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidget) { 224 LauncherAppWidgetProviderInfo info = LauncherAppWidgetProviderInfo.fromProviderInfo( 225 mContext, appWidget); 226 super.onProviderChanged(appWidgetId, info); 227 // The super method updates the dimensions of the providerInfo. Update the 228 // launcher spans accordingly. 229 info.initSpans(mContext); 230 } 231 232 @Override 233 public void deleteAppWidgetId(int appWidgetId) { 234 super.deleteAppWidgetId(appWidgetId); 235 mViews.remove(appWidgetId); 236 } 237 238 @Override 239 public void clearViews() { 240 super.clearViews(); 241 mViews.clear(); 242 } 243 244 public void startBindFlow(BaseActivity activity, 245 int appWidgetId, AppWidgetProviderInfo info, int requestCode) { 246 247 if (FeatureFlags.GO_DISABLE_WIDGETS) { 248 sendActionCancelled(activity, requestCode); 249 return; 250 } 251 252 Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND) 253 .putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId) 254 .putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, info.provider) 255 .putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE, info.getProfile()); 256 // TODO: we need to make sure that this accounts for the options bundle. 257 // intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, options); 258 activity.startActivityForResult(intent, requestCode); 259 } 260 261 262 public void startConfigActivity(BaseActivity activity, int widgetId, int requestCode) { 263 if (FeatureFlags.GO_DISABLE_WIDGETS) { 264 sendActionCancelled(activity, requestCode); 265 return; 266 } 267 268 try { 269 startAppWidgetConfigureActivityForResult(activity, widgetId, 0, requestCode, null); 270 } catch (ActivityNotFoundException | SecurityException e) { 271 Toast.makeText(activity, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); 272 sendActionCancelled(activity, requestCode); 273 } 274 } 275 276 private void sendActionCancelled(final BaseActivity activity, final int requestCode) { 277 new Handler().post(() -> activity.onActivityResult(requestCode, RESULT_CANCELED, null)); 278 } 279 280 /** 281 * Listener for getting notifications on provider changes. 282 */ 283 public interface ProviderChangedListener { 284 285 void notifyWidgetProvidersChanged(); 286 } 287} 288