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 android.appwidget; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.app.Activity; 22 import android.content.ActivityNotFoundException; 23 import android.content.Context; 24 import android.content.IntentSender; 25 import android.content.pm.PackageManager; 26 import android.os.Binder; 27 import android.os.Bundle; 28 import android.os.Handler; 29 import android.os.IBinder; 30 import android.os.Looper; 31 import android.os.Message; 32 import android.os.Process; 33 import android.os.RemoteException; 34 import android.os.ServiceManager; 35 import android.util.DisplayMetrics; 36 import android.util.SparseArray; 37 import android.widget.RemoteViews; 38 import android.widget.RemoteViews.OnClickHandler; 39 40 import com.android.internal.appwidget.IAppWidgetHost; 41 import com.android.internal.appwidget.IAppWidgetService; 42 43 import java.lang.ref.WeakReference; 44 import java.util.List; 45 46 /** 47 * AppWidgetHost provides the interaction with the AppWidget service for apps, 48 * like the home screen, that want to embed AppWidgets in their UI. 49 */ 50 public class AppWidgetHost { 51 52 static final int HANDLE_UPDATE = 1; 53 static final int HANDLE_PROVIDER_CHANGED = 2; 54 static final int HANDLE_PROVIDERS_CHANGED = 3; 55 static final int HANDLE_VIEW_DATA_CHANGED = 4; 56 57 final static Object sServiceLock = new Object(); 58 static IAppWidgetService sService; 59 static boolean sServiceInitialized = false; 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(context); 166 } 167 168 private static void bindService(Context context) { 169 synchronized (sServiceLock) { 170 if (sServiceInitialized) { 171 return; 172 } 173 sServiceInitialized = true; 174 if (!context.getPackageManager().hasSystemFeature( 175 PackageManager.FEATURE_APP_WIDGETS)) { 176 return; 177 } 178 IBinder b = ServiceManager.getService(Context.APPWIDGET_SERVICE); 179 sService = IAppWidgetService.Stub.asInterface(b); 180 } 181 } 182 183 /** 184 * Start receiving onAppWidgetChanged calls for your AppWidgets. Call this when your activity 185 * becomes visible, i.e. from onStart() in your Activity. 186 */ 187 public void startListening() { 188 if (sService == null) { 189 return; 190 } 191 final int[] idsToUpdate; 192 synchronized (mViews) { 193 int N = mViews.size(); 194 idsToUpdate = new int[N]; 195 for (int i = 0; i < N; i++) { 196 idsToUpdate[i] = mViews.keyAt(i); 197 } 198 } 199 List<PendingHostUpdate> updates; 200 try { 201 updates = sService.startListening( 202 mCallbacks, mContextOpPackageName, mHostId, idsToUpdate).getList(); 203 } 204 catch (RemoteException e) { 205 throw new RuntimeException("system server dead?", e); 206 } 207 208 int N = updates.size(); 209 for (int i = 0; i < N; i++) { 210 PendingHostUpdate update = updates.get(i); 211 switch (update.type) { 212 case PendingHostUpdate.TYPE_VIEWS_UPDATE: 213 updateAppWidgetView(update.appWidgetId, update.views); 214 break; 215 case PendingHostUpdate.TYPE_PROVIDER_CHANGED: 216 onProviderChanged(update.appWidgetId, update.widgetInfo); 217 break; 218 case PendingHostUpdate.TYPE_VIEW_DATA_CHANGED: 219 viewDataChanged(update.appWidgetId, update.viewId); 220 } 221 } 222 } 223 224 /** 225 * Stop receiving onAppWidgetChanged calls for your AppWidgets. Call this when your activity is 226 * no longer visible, i.e. from onStop() in your Activity. 227 */ 228 public void stopListening() { 229 if (sService == null) { 230 return; 231 } 232 try { 233 sService.stopListening(mContextOpPackageName, mHostId); 234 } 235 catch (RemoteException e) { 236 throw new RuntimeException("system server dead?", e); 237 } 238 } 239 240 /** 241 * Get a appWidgetId for a host in the calling process. 242 * 243 * @return a appWidgetId 244 */ 245 public int allocateAppWidgetId() { 246 if (sService == null) { 247 return -1; 248 } 249 try { 250 return sService.allocateAppWidgetId(mContextOpPackageName, mHostId); 251 } 252 catch (RemoteException e) { 253 throw new RuntimeException("system server dead?", e); 254 } 255 } 256 257 /** 258 * Starts an app widget provider configure activity for result on behalf of the caller. 259 * Use this method if the provider is in another profile as you are not allowed to start 260 * an activity in another profile. You can optionally provide a request code that is 261 * returned in {@link Activity#onActivityResult(int, int, android.content.Intent)} and 262 * an options bundle to be passed to the started activity. 263 * <p> 264 * Note that the provided app widget has to be bound for this method to work. 265 * </p> 266 * 267 * @param activity The activity from which to start the configure one. 268 * @param appWidgetId The bound app widget whose provider's config activity to start. 269 * @param requestCode Optional request code retuned with the result. 270 * @param intentFlags Optional intent flags. 271 * 272 * @throws android.content.ActivityNotFoundException If the activity is not found. 273 * 274 * @see AppWidgetProviderInfo#getProfile() 275 */ 276 public final void startAppWidgetConfigureActivityForResult(@NonNull Activity activity, 277 int appWidgetId, int intentFlags, int requestCode, @Nullable Bundle options) { 278 if (sService == null) { 279 return; 280 } 281 try { 282 IntentSender intentSender = sService.createAppWidgetConfigIntentSender( 283 mContextOpPackageName, appWidgetId, intentFlags); 284 if (intentSender != null) { 285 activity.startIntentSenderForResult(intentSender, requestCode, null, 0, 0, 0, 286 options); 287 } else { 288 throw new ActivityNotFoundException(); 289 } 290 } catch (IntentSender.SendIntentException e) { 291 throw new ActivityNotFoundException(); 292 } catch (RemoteException e) { 293 throw new RuntimeException("system server dead?", e); 294 } 295 } 296 297 /** 298 * Gets a list of all the appWidgetIds that are bound to the current host 299 */ 300 public int[] getAppWidgetIds() { 301 if (sService == null) { 302 return new int[0]; 303 } 304 try { 305 return sService.getAppWidgetIdsForHost(mContextOpPackageName, mHostId); 306 } catch (RemoteException e) { 307 throw new RuntimeException("system server dead?", e); 308 } 309 } 310 311 /** 312 * Stop listening to changes for this AppWidget. 313 */ 314 public void deleteAppWidgetId(int appWidgetId) { 315 if (sService == null) { 316 return; 317 } 318 synchronized (mViews) { 319 mViews.remove(appWidgetId); 320 try { 321 sService.deleteAppWidgetId(mContextOpPackageName, appWidgetId); 322 } 323 catch (RemoteException e) { 324 throw new RuntimeException("system server dead?", e); 325 } 326 } 327 } 328 329 /** 330 * Remove all records about this host from the AppWidget manager. 331 * <ul> 332 * <li>Call this when initializing your database, as it might be because of a data wipe.</li> 333 * <li>Call this to have the AppWidget manager release all resources associated with your 334 * host. Any future calls about this host will cause the records to be re-allocated.</li> 335 * </ul> 336 */ 337 public void deleteHost() { 338 if (sService == null) { 339 return; 340 } 341 try { 342 sService.deleteHost(mContextOpPackageName, mHostId); 343 } 344 catch (RemoteException e) { 345 throw new RuntimeException("system server dead?", e); 346 } 347 } 348 349 /** 350 * Remove all records about all hosts for your package. 351 * <ul> 352 * <li>Call this when initializing your database, as it might be because of a data wipe.</li> 353 * <li>Call this to have the AppWidget manager release all resources associated with your 354 * host. Any future calls about this host will cause the records to be re-allocated.</li> 355 * </ul> 356 */ 357 public static void deleteAllHosts() { 358 if (sService == null) { 359 return; 360 } 361 try { 362 sService.deleteAllHosts(); 363 } 364 catch (RemoteException e) { 365 throw new RuntimeException("system server dead?", e); 366 } 367 } 368 369 /** 370 * Create the AppWidgetHostView for the given widget. 371 * The AppWidgetHost retains a pointer to the newly-created View. 372 */ 373 public final AppWidgetHostView createView(Context context, int appWidgetId, 374 AppWidgetProviderInfo appWidget) { 375 if (sService == null) { 376 return null; 377 } 378 AppWidgetHostView view = onCreateView(context, appWidgetId, appWidget); 379 view.setOnClickHandler(mOnClickHandler); 380 view.setAppWidget(appWidgetId, appWidget); 381 synchronized (mViews) { 382 mViews.put(appWidgetId, view); 383 } 384 RemoteViews views; 385 try { 386 views = sService.getAppWidgetViews(mContextOpPackageName, appWidgetId); 387 } catch (RemoteException e) { 388 throw new RuntimeException("system server dead?", e); 389 } 390 view.updateAppWidget(views); 391 392 return view; 393 } 394 395 /** 396 * Called to create the AppWidgetHostView. Override to return a custom subclass if you 397 * need it. {@more} 398 */ 399 protected AppWidgetHostView onCreateView(Context context, int appWidgetId, 400 AppWidgetProviderInfo appWidget) { 401 return new AppWidgetHostView(context, mOnClickHandler); 402 } 403 404 /** 405 * Called when the AppWidget provider for a AppWidget has been upgraded to a new apk. 406 */ 407 protected void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidget) { 408 AppWidgetHostView v; 409 410 // Convert complex to dp -- we are getting the AppWidgetProviderInfo from the 411 // AppWidgetService, which doesn't have our context, hence we need to do the 412 // conversion here. 413 appWidget.updateDimensions(mDisplayMetrics); 414 synchronized (mViews) { 415 v = mViews.get(appWidgetId); 416 } 417 if (v != null) { 418 v.resetAppWidget(appWidget); 419 } 420 } 421 422 /** 423 * Called when the set of available widgets changes (ie. widget containing packages 424 * are added, updated or removed, or widget components are enabled or disabled.) 425 */ 426 protected void onProvidersChanged() { 427 // Does nothing 428 } 429 430 void updateAppWidgetView(int appWidgetId, RemoteViews views) { 431 AppWidgetHostView v; 432 synchronized (mViews) { 433 v = mViews.get(appWidgetId); 434 } 435 if (v != null) { 436 v.updateAppWidget(views); 437 } 438 } 439 440 void viewDataChanged(int appWidgetId, int viewId) { 441 AppWidgetHostView v; 442 synchronized (mViews) { 443 v = mViews.get(appWidgetId); 444 } 445 if (v != null) { 446 v.viewDataChanged(viewId); 447 } 448 } 449 450 /** 451 * Clear the list of Views that have been created by this AppWidgetHost. 452 */ 453 protected void clearViews() { 454 synchronized (mViews) { 455 mViews.clear(); 456 } 457 } 458 } 459 460 461