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