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 android.appwidget; 18 19import java.lang.ref.WeakReference; 20import java.util.List; 21 22import android.annotation.NonNull; 23import android.annotation.Nullable; 24import android.app.Activity; 25import android.content.ActivityNotFoundException; 26import android.content.Context; 27import android.content.IntentSender; 28import android.os.Binder; 29import android.os.Bundle; 30import android.os.Handler; 31import android.os.IBinder; 32import android.os.Looper; 33import android.os.Message; 34import android.os.Process; 35import android.os.RemoteException; 36import android.os.ServiceManager; 37import android.os.SystemClock; 38import android.util.DisplayMetrics; 39import android.util.SparseArray; 40import android.util.TypedValue; 41import android.widget.RemoteViews; 42import android.widget.RemoteViews.OnClickHandler; 43 44import com.android.internal.appwidget.IAppWidgetHost; 45import com.android.internal.appwidget.IAppWidgetService; 46 47/** 48 * AppWidgetHost provides the interaction with the AppWidget service for apps, 49 * like the home screen, that want to embed AppWidgets in their UI. 50 */ 51public class AppWidgetHost { 52 53 static final int HANDLE_UPDATE = 1; 54 static final int HANDLE_PROVIDER_CHANGED = 2; 55 static final int HANDLE_PROVIDERS_CHANGED = 3; 56 static final int HANDLE_VIEW_DATA_CHANGED = 4; 57 58 final static Object sServiceLock = new Object(); 59 static IAppWidgetService sService; 60 private DisplayMetrics mDisplayMetrics; 61 62 private String mContextOpPackageName; 63 private final Handler mHandler; 64 private final int mHostId; 65 private final Callbacks mCallbacks; 66 private final SparseArray<AppWidgetHostView> mViews = new SparseArray<>(); 67 private OnClickHandler mOnClickHandler; 68 69 static class Callbacks extends IAppWidgetHost.Stub { 70 private final WeakReference<Handler> mWeakHandler; 71 72 public Callbacks(Handler handler) { 73 mWeakHandler = new WeakReference<>(handler); 74 } 75 76 public void updateAppWidget(int appWidgetId, RemoteViews views) { 77 if (isLocalBinder() && views != null) { 78 views = views.clone(); 79 } 80 Handler handler = mWeakHandler.get(); 81 if (handler == null) { 82 return; 83 } 84 Message msg = handler.obtainMessage(HANDLE_UPDATE, appWidgetId, 0, views); 85 msg.sendToTarget(); 86 } 87 88 public void providerChanged(int appWidgetId, AppWidgetProviderInfo info) { 89 if (isLocalBinder() && info != null) { 90 info = info.clone(); 91 } 92 Handler handler = mWeakHandler.get(); 93 if (handler == null) { 94 return; 95 } 96 Message msg = handler.obtainMessage(HANDLE_PROVIDER_CHANGED, 97 appWidgetId, 0, info); 98 msg.sendToTarget(); 99 } 100 101 public void providersChanged() { 102 Handler handler = mWeakHandler.get(); 103 if (handler == null) { 104 return; 105 } 106 handler.obtainMessage(HANDLE_PROVIDERS_CHANGED).sendToTarget(); 107 } 108 109 public void viewDataChanged(int appWidgetId, int viewId) { 110 Handler handler = mWeakHandler.get(); 111 if (handler == null) { 112 return; 113 } 114 Message msg = handler.obtainMessage(HANDLE_VIEW_DATA_CHANGED, 115 appWidgetId, viewId); 116 msg.sendToTarget(); 117 } 118 119 private static boolean isLocalBinder() { 120 return Process.myPid() == Binder.getCallingPid(); 121 } 122 } 123 124 class UpdateHandler extends Handler { 125 public UpdateHandler(Looper looper) { 126 super(looper); 127 } 128 129 public void handleMessage(Message msg) { 130 switch (msg.what) { 131 case HANDLE_UPDATE: { 132 updateAppWidgetView(msg.arg1, (RemoteViews)msg.obj); 133 break; 134 } 135 case HANDLE_PROVIDER_CHANGED: { 136 onProviderChanged(msg.arg1, (AppWidgetProviderInfo)msg.obj); 137 break; 138 } 139 case HANDLE_PROVIDERS_CHANGED: { 140 onProvidersChanged(); 141 break; 142 } 143 case HANDLE_VIEW_DATA_CHANGED: { 144 viewDataChanged(msg.arg1, msg.arg2); 145 break; 146 } 147 } 148 } 149 } 150 151 public AppWidgetHost(Context context, int hostId) { 152 this(context, hostId, null, context.getMainLooper()); 153 } 154 155 /** 156 * @hide 157 */ 158 public AppWidgetHost(Context context, int hostId, OnClickHandler handler, Looper looper) { 159 mContextOpPackageName = context.getOpPackageName(); 160 mHostId = hostId; 161 mOnClickHandler = handler; 162 mHandler = new UpdateHandler(looper); 163 mCallbacks = new Callbacks(mHandler); 164 mDisplayMetrics = context.getResources().getDisplayMetrics(); 165 bindService(); 166 } 167 168 private static void bindService() { 169 synchronized (sServiceLock) { 170 if (sService == null) { 171 IBinder b = ServiceManager.getService(Context.APPWIDGET_SERVICE); 172 sService = IAppWidgetService.Stub.asInterface(b); 173 } 174 } 175 } 176 177 /** 178 * Start receiving onAppWidgetChanged calls for your AppWidgets. Call this when your activity 179 * becomes visible, i.e. from onStart() in your Activity. 180 */ 181 public void startListening() { 182 final int[] idsToUpdate; 183 synchronized (mViews) { 184 int N = mViews.size(); 185 idsToUpdate = new int[N]; 186 for (int i = 0; i < N; i++) { 187 idsToUpdate[i] = mViews.keyAt(i); 188 } 189 } 190 List<RemoteViews> updatedViews; 191 int[] updatedIds = new int[idsToUpdate.length]; 192 try { 193 updatedViews = sService.startListening( 194 mCallbacks, mContextOpPackageName, mHostId, idsToUpdate, updatedIds).getList(); 195 } 196 catch (RemoteException e) { 197 throw new RuntimeException("system server dead?", e); 198 } 199 200 int N = updatedViews.size(); 201 for (int i = 0; i < N; i++) { 202 updateAppWidgetView(updatedIds[i], updatedViews.get(i)); 203 } 204 } 205 206 /** 207 * Stop receiving onAppWidgetChanged calls for your AppWidgets. Call this when your activity is 208 * no longer visible, i.e. from onStop() in your Activity. 209 */ 210 public void stopListening() { 211 try { 212 sService.stopListening(mContextOpPackageName, mHostId); 213 } 214 catch (RemoteException e) { 215 throw new RuntimeException("system server dead?", e); 216 } 217 } 218 219 /** 220 * Get a appWidgetId for a host in the calling process. 221 * 222 * @return a appWidgetId 223 */ 224 public int allocateAppWidgetId() { 225 try { 226 return sService.allocateAppWidgetId(mContextOpPackageName, mHostId); 227 } 228 catch (RemoteException e) { 229 throw new RuntimeException("system server dead?", e); 230 } 231 } 232 233 /** 234 * Starts an app widget provider configure activity for result on behalf of the caller. 235 * Use this method if the provider is in another profile as you are not allowed to start 236 * an activity in another profile. You can optionally provide a request code that is 237 * returned in {@link Activity#onActivityResult(int, int, android.content.Intent)} and 238 * an options bundle to be passed to the started activity. 239 * <p> 240 * Note that the provided app widget has to be bound for this method to work. 241 * </p> 242 * 243 * @param activity The activity from which to start the configure one. 244 * @param appWidgetId The bound app widget whose provider's config activity to start. 245 * @param requestCode Optional request code retuned with the result. 246 * @param intentFlags Optional intent flags. 247 * 248 * @throws android.content.ActivityNotFoundException If the activity is not found. 249 * 250 * @see AppWidgetProviderInfo#getProfile() 251 */ 252 public final void startAppWidgetConfigureActivityForResult(@NonNull Activity activity, 253 int appWidgetId, int intentFlags, int requestCode, @Nullable Bundle options) { 254 try { 255 IntentSender intentSender = sService.createAppWidgetConfigIntentSender( 256 mContextOpPackageName, appWidgetId, intentFlags); 257 if (intentSender != null) { 258 activity.startIntentSenderForResult(intentSender, requestCode, null, 0, 0, 0, 259 options); 260 } else { 261 throw new ActivityNotFoundException(); 262 } 263 } catch (IntentSender.SendIntentException e) { 264 throw new ActivityNotFoundException(); 265 } catch (RemoteException e) { 266 throw new RuntimeException("system server dead?", e); 267 } 268 } 269 270 /** 271 * Gets a list of all the appWidgetIds that are bound to the current host 272 * 273 * @hide 274 */ 275 public int[] getAppWidgetIds() { 276 try { 277 if (sService == null) { 278 bindService(); 279 } 280 return sService.getAppWidgetIdsForHost(mContextOpPackageName, mHostId); 281 } catch (RemoteException e) { 282 throw new RuntimeException("system server dead?", e); 283 } 284 } 285 286 /** 287 * Stop listening to changes for this AppWidget. 288 */ 289 public void deleteAppWidgetId(int appWidgetId) { 290 synchronized (mViews) { 291 mViews.remove(appWidgetId); 292 try { 293 sService.deleteAppWidgetId(mContextOpPackageName, appWidgetId); 294 } 295 catch (RemoteException e) { 296 throw new RuntimeException("system server dead?", e); 297 } 298 } 299 } 300 301 /** 302 * Remove all records about this host from the AppWidget manager. 303 * <ul> 304 * <li>Call this when initializing your database, as it might be because of a data wipe.</li> 305 * <li>Call this to have the AppWidget manager release all resources associated with your 306 * host. Any future calls about this host will cause the records to be re-allocated.</li> 307 * </ul> 308 */ 309 public void deleteHost() { 310 try { 311 sService.deleteHost(mContextOpPackageName, mHostId); 312 } 313 catch (RemoteException e) { 314 throw new RuntimeException("system server dead?", e); 315 } 316 } 317 318 /** 319 * Remove all records about all hosts for your package. 320 * <ul> 321 * <li>Call this when initializing your database, as it might be because of a data wipe.</li> 322 * <li>Call this to have the AppWidget manager release all resources associated with your 323 * host. Any future calls about this host will cause the records to be re-allocated.</li> 324 * </ul> 325 */ 326 public static void deleteAllHosts() { 327 try { 328 sService.deleteAllHosts(); 329 } 330 catch (RemoteException e) { 331 throw new RuntimeException("system server dead?", e); 332 } 333 } 334 335 /** 336 * Create the AppWidgetHostView for the given widget. 337 * The AppWidgetHost retains a pointer to the newly-created View. 338 */ 339 public final AppWidgetHostView createView(Context context, int appWidgetId, 340 AppWidgetProviderInfo appWidget) { 341 AppWidgetHostView view = onCreateView(context, appWidgetId, appWidget); 342 view.setOnClickHandler(mOnClickHandler); 343 view.setAppWidget(appWidgetId, appWidget); 344 synchronized (mViews) { 345 mViews.put(appWidgetId, view); 346 } 347 RemoteViews views; 348 try { 349 views = sService.getAppWidgetViews(mContextOpPackageName, appWidgetId); 350 } catch (RemoteException e) { 351 throw new RuntimeException("system server dead?", e); 352 } 353 view.updateAppWidget(views); 354 355 return view; 356 } 357 358 /** 359 * Called to create the AppWidgetHostView. Override to return a custom subclass if you 360 * need it. {@more} 361 */ 362 protected AppWidgetHostView onCreateView(Context context, int appWidgetId, 363 AppWidgetProviderInfo appWidget) { 364 return new AppWidgetHostView(context, mOnClickHandler); 365 } 366 367 /** 368 * Called when the AppWidget provider for a AppWidget has been upgraded to a new apk. 369 */ 370 protected void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidget) { 371 AppWidgetHostView v; 372 373 // Convert complex to dp -- we are getting the AppWidgetProviderInfo from the 374 // AppWidgetService, which doesn't have our context, hence we need to do the 375 // conversion here. 376 appWidget.minWidth = 377 TypedValue.complexToDimensionPixelSize(appWidget.minWidth, mDisplayMetrics); 378 appWidget.minHeight = 379 TypedValue.complexToDimensionPixelSize(appWidget.minHeight, mDisplayMetrics); 380 appWidget.minResizeWidth = 381 TypedValue.complexToDimensionPixelSize(appWidget.minResizeWidth, mDisplayMetrics); 382 appWidget.minResizeHeight = 383 TypedValue.complexToDimensionPixelSize(appWidget.minResizeHeight, mDisplayMetrics); 384 385 synchronized (mViews) { 386 v = mViews.get(appWidgetId); 387 } 388 if (v != null) { 389 v.resetAppWidget(appWidget); 390 } 391 } 392 393 /** 394 * Called when the set of available widgets changes (ie. widget containing packages 395 * are added, updated or removed, or widget components are enabled or disabled.) 396 */ 397 protected void onProvidersChanged() { 398 // Does nothing 399 } 400 401 void updateAppWidgetView(int appWidgetId, RemoteViews views) { 402 AppWidgetHostView v; 403 synchronized (mViews) { 404 v = mViews.get(appWidgetId); 405 } 406 if (v != null) { 407 v.updateAppWidget(views); 408 } 409 } 410 411 void viewDataChanged(int appWidgetId, int viewId) { 412 AppWidgetHostView v; 413 synchronized (mViews) { 414 v = mViews.get(appWidgetId); 415 } 416 if (v != null) { 417 v.viewDataChanged(viewId); 418 } 419 } 420 421 /** 422 * Clear the list of Views that have been created by this AppWidgetHost. 423 */ 424 protected void clearViews() { 425 synchronized (mViews) { 426 mViews.clear(); 427 } 428 } 429} 430 431 432