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.app.ActivityThread; 23 import android.content.Context; 24 import android.os.Binder; 25 import android.os.Handler; 26 import android.os.IBinder; 27 import android.os.Looper; 28 import android.os.Message; 29 import android.os.Process; 30 import android.os.RemoteException; 31 import android.os.ServiceManager; 32 import android.os.UserHandle; 33 import android.util.DisplayMetrics; 34 import android.util.Log; 35 import android.util.TypedValue; 36 import android.widget.RemoteViews; 37 import android.widget.RemoteViews.OnClickHandler; 38 39 import com.android.internal.appwidget.IAppWidgetHost; 40 import com.android.internal.appwidget.IAppWidgetService; 41 42 /** 43 * AppWidgetHost provides the interaction with the AppWidget service for apps, 44 * like the home screen, that want to embed AppWidgets in their UI. 45 */ 46 public class AppWidgetHost { 47 48 static final int HANDLE_UPDATE = 1; 49 static final int HANDLE_PROVIDER_CHANGED = 2; 50 static final int HANDLE_PROVIDERS_CHANGED = 3; 51 static final int HANDLE_VIEW_DATA_CHANGED = 4; 52 53 final static Object sServiceLock = new Object(); 54 static IAppWidgetService sService; 55 private DisplayMetrics mDisplayMetrics; 56 57 Context mContext; 58 String mPackageName; 59 Handler mHandler; 60 int mHostId; 61 Callbacks mCallbacks = new Callbacks(); 62 final HashMap<Integer,AppWidgetHostView> mViews = new HashMap<Integer, AppWidgetHostView>(); 63 private OnClickHandler mOnClickHandler; 64 65 class Callbacks extends IAppWidgetHost.Stub { updateAppWidget(int appWidgetId, RemoteViews views, int userId)66 public void updateAppWidget(int appWidgetId, RemoteViews views, int userId) { 67 if (isLocalBinder() && views != null) { 68 views = views.clone(); 69 views.setUser(new UserHandle(userId)); 70 } 71 Message msg = mHandler.obtainMessage(HANDLE_UPDATE, appWidgetId, userId, views); 72 msg.sendToTarget(); 73 } 74 providerChanged(int appWidgetId, AppWidgetProviderInfo info, int userId)75 public void providerChanged(int appWidgetId, AppWidgetProviderInfo info, int userId) { 76 if (isLocalBinder() && info != null) { 77 info = info.clone(); 78 } 79 Message msg = mHandler.obtainMessage(HANDLE_PROVIDER_CHANGED, 80 appWidgetId, userId, info); 81 msg.sendToTarget(); 82 } 83 providersChanged(int userId)84 public void providersChanged(int userId) { 85 Message msg = mHandler.obtainMessage(HANDLE_PROVIDERS_CHANGED, userId, 0); 86 msg.sendToTarget(); 87 } 88 viewDataChanged(int appWidgetId, int viewId, int userId)89 public void viewDataChanged(int appWidgetId, int viewId, int userId) { 90 Message msg = mHandler.obtainMessage(HANDLE_VIEW_DATA_CHANGED, 91 appWidgetId, viewId, userId); 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, msg.arg2); 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, (Integer) msg.obj); 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 mContext = context; 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 157 final int userId = mContext.getUserId(); 158 try { 159 if (mPackageName == null) { 160 mPackageName = mContext.getPackageName(); 161 } 162 updatedIds = sService.startListening( 163 mCallbacks, mPackageName, mHostId, updatedViews, userId); 164 } 165 catch (RemoteException e) { 166 throw new RuntimeException("system server dead?", e); 167 } 168 169 final int N = updatedIds.length; 170 for (int i=0; i<N; i++) { 171 if (updatedViews.get(i) != null) { 172 updatedViews.get(i).setUser(new UserHandle(userId)); 173 } 174 updateAppWidgetView(updatedIds[i], updatedViews.get(i), userId); 175 } 176 } 177 178 /** 179 * Stop receiving onAppWidgetChanged calls for your AppWidgets. Call this when your activity is 180 * no longer visible, i.e. from onStop() in your Activity. 181 */ stopListening()182 public void stopListening() { 183 try { 184 sService.stopListening(mHostId, mContext.getUserId()); 185 } 186 catch (RemoteException e) { 187 throw new RuntimeException("system server dead?", e); 188 } 189 190 // This is here because keyguard needs it since it'll be switching users after this call. 191 // If it turns out other apps need to call this often, we should re-think how this works. 192 clearViews(); 193 } 194 195 /** 196 * Get a appWidgetId for a host in the calling process. 197 * 198 * @return a appWidgetId 199 */ allocateAppWidgetId()200 public int allocateAppWidgetId() { 201 202 try { 203 if (mPackageName == null) { 204 mPackageName = mContext.getPackageName(); 205 } 206 return sService.allocateAppWidgetId(mPackageName, mHostId, mContext.getUserId()); 207 } 208 catch (RemoteException e) { 209 throw new RuntimeException("system server dead?", e); 210 } 211 } 212 213 /** 214 * Get a appWidgetId for a host in the calling process. 215 * 216 * @return a appWidgetId 217 * @hide 218 */ allocateAppWidgetIdForSystem(int hostId, int userId)219 public static int allocateAppWidgetIdForSystem(int hostId, int userId) { 220 checkCallerIsSystem(); 221 try { 222 if (sService == null) { 223 bindService(); 224 } 225 Context systemContext = 226 (Context) ActivityThread.currentActivityThread().getSystemContext(); 227 String packageName = systemContext.getPackageName(); 228 return sService.allocateAppWidgetId(packageName, hostId, userId); 229 } catch (RemoteException e) { 230 throw new RuntimeException("system server dead?", e); 231 } 232 } 233 234 /** 235 * Gets a list of all the appWidgetIds that are bound to the current host 236 * 237 * @hide 238 */ getAppWidgetIds()239 public int[] getAppWidgetIds() { 240 try { 241 if (sService == null) { 242 bindService(); 243 } 244 return sService.getAppWidgetIdsForHost(mHostId, mContext.getUserId()); 245 } catch (RemoteException e) { 246 throw new RuntimeException("system server dead?", e); 247 } 248 } 249 checkCallerIsSystem()250 private static void checkCallerIsSystem() { 251 int uid = Process.myUid(); 252 if (UserHandle.getAppId(uid) == Process.SYSTEM_UID || uid == 0) { 253 return; 254 } 255 throw new SecurityException("Disallowed call for uid " + uid); 256 } 257 isLocalBinder()258 private boolean isLocalBinder() { 259 return Process.myPid() == Binder.getCallingPid(); 260 } 261 262 /** 263 * Stop listening to changes for this AppWidget. 264 */ deleteAppWidgetId(int appWidgetId)265 public void deleteAppWidgetId(int appWidgetId) { 266 synchronized (mViews) { 267 mViews.remove(appWidgetId); 268 try { 269 sService.deleteAppWidgetId(appWidgetId, mContext.getUserId()); 270 } 271 catch (RemoteException e) { 272 throw new RuntimeException("system server dead?", e); 273 } 274 } 275 } 276 277 /** 278 * Stop listening to changes for this AppWidget. 279 * @hide 280 */ deleteAppWidgetIdForSystem(int appWidgetId, int userId)281 public static void deleteAppWidgetIdForSystem(int appWidgetId, int userId) { 282 checkCallerIsSystem(); 283 try { 284 if (sService == null) { 285 bindService(); 286 } 287 sService.deleteAppWidgetId(appWidgetId, userId); 288 } catch (RemoteException e) { 289 throw new RuntimeException("system server dead?", e); 290 } 291 } 292 293 /** 294 * Remove all records about this host from the AppWidget manager. 295 * <ul> 296 * <li>Call this when initializing your database, as it might be because of a data wipe.</li> 297 * <li>Call this to have the AppWidget manager release all resources associated with your 298 * host. Any future calls about this host will cause the records to be re-allocated.</li> 299 * </ul> 300 */ deleteHost()301 public void deleteHost() { 302 try { 303 sService.deleteHost(mHostId, mContext.getUserId()); 304 } 305 catch (RemoteException e) { 306 throw new RuntimeException("system server dead?", e); 307 } 308 } 309 310 /** 311 * Remove all records about all hosts for your package. 312 * <ul> 313 * <li>Call this when initializing your database, as it might be because of a data wipe.</li> 314 * <li>Call this to have the AppWidget manager release all resources associated with your 315 * host. Any future calls about this host will cause the records to be re-allocated.</li> 316 * </ul> 317 */ deleteAllHosts()318 public static void deleteAllHosts() { 319 deleteAllHosts(UserHandle.myUserId()); 320 } 321 322 /** 323 * Private method containing a userId 324 * @hide 325 */ deleteAllHosts(int userId)326 public static void deleteAllHosts(int userId) { 327 try { 328 sService.deleteAllHosts(userId); 329 } 330 catch (RemoteException e) { 331 throw new RuntimeException("system server dead?", e); 332 } 333 } 334 335 /** 336 * Create the AppWidgetHostView for the given widget. 337 * The AppWidgetHost retains a pointer to the newly-created View. 338 */ createView(Context context, int appWidgetId, AppWidgetProviderInfo appWidget)339 public final AppWidgetHostView createView(Context context, int appWidgetId, 340 AppWidgetProviderInfo appWidget) { 341 final int userId = mContext.getUserId(); 342 AppWidgetHostView view = onCreateView(mContext, appWidgetId, appWidget); 343 view.setUserId(userId); 344 view.setOnClickHandler(mOnClickHandler); 345 view.setAppWidget(appWidgetId, appWidget); 346 synchronized (mViews) { 347 mViews.put(appWidgetId, view); 348 } 349 RemoteViews views; 350 try { 351 views = sService.getAppWidgetViews(appWidgetId, userId); 352 if (views != null) { 353 views.setUser(new UserHandle(mContext.getUserId())); 354 } 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.minWidth = 382 TypedValue.complexToDimensionPixelSize(appWidget.minWidth, mDisplayMetrics); 383 appWidget.minHeight = 384 TypedValue.complexToDimensionPixelSize(appWidget.minHeight, mDisplayMetrics); 385 appWidget.minResizeWidth = 386 TypedValue.complexToDimensionPixelSize(appWidget.minResizeWidth, mDisplayMetrics); 387 appWidget.minResizeHeight = 388 TypedValue.complexToDimensionPixelSize(appWidget.minResizeHeight, mDisplayMetrics); 389 390 synchronized (mViews) { 391 v = mViews.get(appWidgetId); 392 } 393 if (v != null) { 394 v.resetAppWidget(appWidget); 395 } 396 } 397 398 /** 399 * Called when the set of available widgets changes (ie. widget containing packages 400 * are added, updated or removed, or widget components are enabled or disabled.) 401 */ onProvidersChanged()402 protected void onProvidersChanged() { 403 // Does nothing 404 } 405 updateAppWidgetView(int appWidgetId, RemoteViews views, int userId)406 void updateAppWidgetView(int appWidgetId, RemoteViews views, int userId) { 407 AppWidgetHostView v; 408 synchronized (mViews) { 409 v = mViews.get(appWidgetId); 410 } 411 if (v != null) { 412 v.updateAppWidget(views); 413 } 414 } 415 viewDataChanged(int appWidgetId, int viewId, int userId)416 void viewDataChanged(int appWidgetId, int viewId, int userId) { 417 AppWidgetHostView v; 418 synchronized (mViews) { 419 v = mViews.get(appWidgetId); 420 } 421 if (v != null) { 422 v.viewDataChanged(viewId); 423 } 424 } 425 426 /** 427 * Clear the list of Views that have been created by this AppWidgetHost. 428 */ clearViews()429 protected void clearViews() { 430 mViews.clear(); 431 } 432 } 433 434 435