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