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.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 {
     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 
     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 
     85         public void providersChanged() {
     86             mHandler.obtainMessage(HANDLE_PROVIDERS_CHANGED).sendToTarget();
     87         }
     88 
     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 {
     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);
    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 
    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         mContextOpPackageName = context.getOpPackageName();
    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         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      */
    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      */
    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      */
    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, intentFlags);
    225             if (intentSender != null) {
    226                 activity.startIntentSenderForResult(intentSender, requestCode, null, 0, 0, 0,
    227                         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      */
    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 
    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(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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    369     protected void onProvidersChanged() {
    370         // Does nothing
    371     }
    372 
    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 
    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      */
    396     protected void clearViews() {
    397         mViews.clear();
    398     }
    399 }
    400 
    401 
    402