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