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