Home | History | Annotate | Download | only in appwidget
      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 {
     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 
     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 
     84         public void providersChanged(int userId) {
     85             Message msg = mHandler.obtainMessage(HANDLE_PROVIDERS_CHANGED, userId, 0);
     86             msg.sendToTarget();
     87         }
     88 
     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 {
     97         public UpdateHandler(Looper looper) {
     98             super(looper);
     99         }
    100 
    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 
    123     public AppWidgetHost(Context context, int hostId) {
    124         this(context, hostId, null, context.getMainLooper());
    125     }
    126 
    127     /**
    128      * @hide
    129      */
    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 
    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      */
    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      */
    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      */
    200     public int allocateAppWidgetId() {
    201         try {
    202             if (mPackageName == null) {
    203                 mPackageName = mContext.getPackageName();
    204             }
    205             return sService.allocateAppWidgetId(mPackageName, mHostId, mContext.getUserId());
    206         }
    207         catch (RemoteException e) {
    208             throw new RuntimeException("system server dead?", e);
    209         }
    210     }
    211 
    212     /**
    213      * Get a appWidgetId for a host in the given package.
    214      *
    215      * @return a appWidgetId
    216      * @hide
    217      */
    218     public static int allocateAppWidgetIdForPackage(int hostId, int userId, String packageName) {
    219         checkCallerIsSystem();
    220         try {
    221             if (sService == null) {
    222                 bindService();
    223             }
    224             return sService.allocateAppWidgetId(packageName, hostId, userId);
    225         } catch (RemoteException e) {
    226             throw new RuntimeException("system server dead?", e);
    227         }
    228     }
    229 
    230     /**
    231      * Gets a list of all the appWidgetIds that are bound to the current host
    232      *
    233      * @hide
    234      */
    235     public int[] getAppWidgetIds() {
    236         try {
    237             if (sService == null) {
    238                 bindService();
    239             }
    240             return sService.getAppWidgetIdsForHost(mHostId, mContext.getUserId());
    241         } catch (RemoteException e) {
    242             throw new RuntimeException("system server dead?", e);
    243         }
    244     }
    245 
    246     private static void checkCallerIsSystem() {
    247         int uid = Process.myUid();
    248         if (UserHandle.getAppId(uid) == Process.SYSTEM_UID || uid == 0) {
    249             return;
    250         }
    251         throw new SecurityException("Disallowed call for uid " + uid);
    252     }
    253 
    254     private boolean isLocalBinder() {
    255         return Process.myPid() == Binder.getCallingPid();
    256     }
    257 
    258     /**
    259      * Stop listening to changes for this AppWidget.
    260      */
    261     public void deleteAppWidgetId(int appWidgetId) {
    262         synchronized (mViews) {
    263             mViews.remove(appWidgetId);
    264             try {
    265                 sService.deleteAppWidgetId(appWidgetId, mContext.getUserId());
    266             }
    267             catch (RemoteException e) {
    268                 throw new RuntimeException("system server dead?", e);
    269             }
    270         }
    271     }
    272 
    273     /**
    274      * Stop listening to changes for this AppWidget.
    275      * @hide
    276      */
    277     public static void deleteAppWidgetIdForSystem(int appWidgetId, int userId) {
    278         checkCallerIsSystem();
    279         try {
    280             if (sService == null) {
    281                 bindService();
    282             }
    283             sService.deleteAppWidgetId(appWidgetId, userId);
    284         } catch (RemoteException e) {
    285             throw new RuntimeException("system server dead?", e);
    286         }
    287     }
    288 
    289     /**
    290      * Remove all records about this host from the AppWidget manager.
    291      * <ul>
    292      *   <li>Call this when initializing your database, as it might be because of a data wipe.</li>
    293      *   <li>Call this to have the AppWidget manager release all resources associated with your
    294      *   host.  Any future calls about this host will cause the records to be re-allocated.</li>
    295      * </ul>
    296      */
    297     public void deleteHost() {
    298         try {
    299             sService.deleteHost(mHostId, mContext.getUserId());
    300         }
    301         catch (RemoteException e) {
    302             throw new RuntimeException("system server dead?", e);
    303         }
    304     }
    305 
    306     /**
    307      * Remove all records about all hosts for your package.
    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      */
    314     public static void deleteAllHosts() {
    315         deleteAllHosts(UserHandle.myUserId());
    316     }
    317 
    318     /**
    319      * Private method containing a userId
    320      * @hide
    321      */
    322     public static void deleteAllHosts(int userId) {
    323         try {
    324             sService.deleteAllHosts(userId);
    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      */
    335     public final AppWidgetHostView createView(Context context, int appWidgetId,
    336             AppWidgetProviderInfo appWidget) {
    337         final int userId = mContext.getUserId();
    338         AppWidgetHostView view = onCreateView(mContext, appWidgetId, appWidget);
    339         view.setUserId(userId);
    340         view.setOnClickHandler(mOnClickHandler);
    341         view.setAppWidget(appWidgetId, appWidget);
    342         synchronized (mViews) {
    343             mViews.put(appWidgetId, view);
    344         }
    345         RemoteViews views;
    346         try {
    347             views = sService.getAppWidgetViews(appWidgetId, userId);
    348             if (views != null) {
    349                 views.setUser(new UserHandle(mContext.getUserId()));
    350             }
    351         } catch (RemoteException e) {
    352             throw new RuntimeException("system server dead?", e);
    353         }
    354         view.updateAppWidget(views);
    355 
    356         return view;
    357     }
    358 
    359     /**
    360      * Called to create the AppWidgetHostView.  Override to return a custom subclass if you
    361      * need it.  {@more}
    362      */
    363     protected AppWidgetHostView onCreateView(Context context, int appWidgetId,
    364             AppWidgetProviderInfo appWidget) {
    365         return new AppWidgetHostView(context, mOnClickHandler);
    366     }
    367 
    368     /**
    369      * Called when the AppWidget provider for a AppWidget has been upgraded to a new apk.
    370      */
    371     protected void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidget) {
    372         AppWidgetHostView v;
    373 
    374         // Convert complex to dp -- we are getting the AppWidgetProviderInfo from the
    375         // AppWidgetService, which doesn't have our context, hence we need to do the
    376         // conversion here.
    377         appWidget.minWidth =
    378             TypedValue.complexToDimensionPixelSize(appWidget.minWidth, mDisplayMetrics);
    379         appWidget.minHeight =
    380             TypedValue.complexToDimensionPixelSize(appWidget.minHeight, mDisplayMetrics);
    381         appWidget.minResizeWidth =
    382             TypedValue.complexToDimensionPixelSize(appWidget.minResizeWidth, mDisplayMetrics);
    383         appWidget.minResizeHeight =
    384             TypedValue.complexToDimensionPixelSize(appWidget.minResizeHeight, mDisplayMetrics);
    385 
    386         synchronized (mViews) {
    387             v = mViews.get(appWidgetId);
    388         }
    389         if (v != null) {
    390             v.resetAppWidget(appWidget);
    391         }
    392     }
    393 
    394     /**
    395      * Called when the set of available widgets changes (ie. widget containing packages
    396      * are added, updated or removed, or widget components are enabled or disabled.)
    397      */
    398     protected void onProvidersChanged() {
    399         // Does nothing
    400     }
    401 
    402     void updateAppWidgetView(int appWidgetId, RemoteViews views, int userId) {
    403         AppWidgetHostView v;
    404         synchronized (mViews) {
    405             v = mViews.get(appWidgetId);
    406         }
    407         if (v != null) {
    408             v.updateAppWidget(views);
    409         }
    410     }
    411 
    412     void viewDataChanged(int appWidgetId, int viewId, int userId) {
    413         AppWidgetHostView v;
    414         synchronized (mViews) {
    415             v = mViews.get(appWidgetId);
    416         }
    417         if (v != null) {
    418             v.viewDataChanged(viewId);
    419         }
    420     }
    421 
    422     /**
    423      * Clear the list of Views that have been created by this AppWidgetHost.
    424      */
    425     protected void clearViews() {
    426         mViews.clear();
    427     }
    428 }
    429 
    430 
    431