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.lang.ref.WeakReference;
     20 import java.util.List;
     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.SparseArray;
     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 SparseArray<AppWidgetHostView> mViews = new SparseArray<>();
     66     private OnClickHandler mOnClickHandler;
     67 
     68     static class Callbacks extends IAppWidgetHost.Stub {
     69         private final WeakReference<Handler> mWeakHandler;
     70 
     71         public Callbacks(Handler handler) {
     72             mWeakHandler = new WeakReference<>(handler);
     73         }
     74 
     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 
     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 
    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 
    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 
    118         private static boolean isLocalBinder() {
    119             return Process.myPid() == Binder.getCallingPid();
    120         }
    121     }
    122 
    123     class UpdateHandler extends Handler {
    124         public UpdateHandler(Looper looper) {
    125             super(looper);
    126         }
    127 
    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 
    150     public AppWidgetHost(Context context, int hostId) {
    151         this(context, hostId, null, context.getMainLooper());
    152     }
    153 
    154     /**
    155      * @hide
    156      */
    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     private static void bindService() {
    168         synchronized (sServiceLock) {
    169             if (sService == null) {
    170                 IBinder b = ServiceManager.getService(Context.APPWIDGET_SERVICE);
    171                 sService = IAppWidgetService.Stub.asInterface(b);
    172             }
    173         }
    174     }
    175 
    176     /**
    177      * Start receiving onAppWidgetChanged calls for your AppWidgets.  Call this when your activity
    178      * becomes visible, i.e. from onStart() in your Activity.
    179      */
    180     public void startListening() {
    181         final int[] idsToUpdate;
    182         synchronized (mViews) {
    183             int N = mViews.size();
    184             idsToUpdate = new int[N];
    185             for (int i = 0; i < N; i++) {
    186                 idsToUpdate[i] = mViews.keyAt(i);
    187             }
    188         }
    189         List<PendingHostUpdate> updates;
    190         try {
    191             updates = sService.startListening(
    192                     mCallbacks, mContextOpPackageName, mHostId, idsToUpdate).getList();
    193         }
    194         catch (RemoteException e) {
    195             throw new RuntimeException("system server dead?", e);
    196         }
    197 
    198         int N = updates.size();
    199         for (int i = 0; i < N; i++) {
    200             PendingHostUpdate update = updates.get(i);
    201             switch (update.type) {
    202                 case PendingHostUpdate.TYPE_VIEWS_UPDATE:
    203                     updateAppWidgetView(update.appWidgetId, update.views);
    204                     break;
    205                 case PendingHostUpdate.TYPE_PROVIDER_CHANGED:
    206                     onProviderChanged(update.appWidgetId, update.widgetInfo);
    207                     break;
    208                 case PendingHostUpdate.TYPE_VIEW_DATA_CHANGED:
    209                     viewDataChanged(update.appWidgetId, update.viewId);
    210             }
    211         }
    212     }
    213 
    214     /**
    215      * Stop receiving onAppWidgetChanged calls for your AppWidgets.  Call this when your activity is
    216      * no longer visible, i.e. from onStop() in your Activity.
    217      */
    218     public void stopListening() {
    219         try {
    220             sService.stopListening(mContextOpPackageName, mHostId);
    221         }
    222         catch (RemoteException e) {
    223             throw new RuntimeException("system server dead?", e);
    224         }
    225     }
    226 
    227     /**
    228      * Get a appWidgetId for a host in the calling process.
    229      *
    230      * @return a appWidgetId
    231      */
    232     public int allocateAppWidgetId() {
    233         try {
    234             return sService.allocateAppWidgetId(mContextOpPackageName, mHostId);
    235         }
    236         catch (RemoteException e) {
    237             throw new RuntimeException("system server dead?", e);
    238         }
    239     }
    240 
    241     /**
    242      * Starts an app widget provider configure activity for result on behalf of the caller.
    243      * Use this method if the provider is in another profile as you are not allowed to start
    244      * an activity in another profile. You can optionally provide a request code that is
    245      * returned in {@link Activity#onActivityResult(int, int, android.content.Intent)} and
    246      * an options bundle to be passed to the started activity.
    247      * <p>
    248      * Note that the provided app widget has to be bound for this method to work.
    249      * </p>
    250      *
    251      * @param activity The activity from which to start the configure one.
    252      * @param appWidgetId The bound app widget whose provider's config activity to start.
    253      * @param requestCode Optional request code retuned with the result.
    254      * @param intentFlags Optional intent flags.
    255      *
    256      * @throws android.content.ActivityNotFoundException If the activity is not found.
    257      *
    258      * @see AppWidgetProviderInfo#getProfile()
    259      */
    260     public final void startAppWidgetConfigureActivityForResult(@NonNull Activity activity,
    261             int appWidgetId, int intentFlags, int requestCode, @Nullable Bundle options) {
    262         try {
    263             IntentSender intentSender = sService.createAppWidgetConfigIntentSender(
    264                     mContextOpPackageName, appWidgetId, intentFlags);
    265             if (intentSender != null) {
    266                 activity.startIntentSenderForResult(intentSender, requestCode, null, 0, 0, 0,
    267                         options);
    268             } else {
    269                 throw new ActivityNotFoundException();
    270             }
    271         } catch (IntentSender.SendIntentException e) {
    272             throw new ActivityNotFoundException();
    273         } catch (RemoteException e) {
    274             throw new RuntimeException("system server dead?", e);
    275         }
    276     }
    277 
    278     /**
    279      * Gets a list of all the appWidgetIds that are bound to the current host
    280      *
    281      * @hide
    282      */
    283     public int[] getAppWidgetIds() {
    284         try {
    285             if (sService == null) {
    286                 bindService();
    287             }
    288             return sService.getAppWidgetIdsForHost(mContextOpPackageName, mHostId);
    289         } catch (RemoteException e) {
    290             throw new RuntimeException("system server dead?", e);
    291         }
    292     }
    293 
    294     /**
    295      * Stop listening to changes for this AppWidget.
    296      */
    297     public void deleteAppWidgetId(int appWidgetId) {
    298         synchronized (mViews) {
    299             mViews.remove(appWidgetId);
    300             try {
    301                 sService.deleteAppWidgetId(mContextOpPackageName, appWidgetId);
    302             }
    303             catch (RemoteException e) {
    304                 throw new RuntimeException("system server dead?", e);
    305             }
    306         }
    307     }
    308 
    309     /**
    310      * Remove all records about this host from the AppWidget manager.
    311      * <ul>
    312      *   <li>Call this when initializing your database, as it might be because of a data wipe.</li>
    313      *   <li>Call this to have the AppWidget manager release all resources associated with your
    314      *   host.  Any future calls about this host will cause the records to be re-allocated.</li>
    315      * </ul>
    316      */
    317     public void deleteHost() {
    318         try {
    319             sService.deleteHost(mContextOpPackageName, mHostId);
    320         }
    321         catch (RemoteException e) {
    322             throw new RuntimeException("system server dead?", e);
    323         }
    324     }
    325 
    326     /**
    327      * Remove all records about all hosts for your package.
    328      * <ul>
    329      *   <li>Call this when initializing your database, as it might be because of a data wipe.</li>
    330      *   <li>Call this to have the AppWidget manager release all resources associated with your
    331      *   host.  Any future calls about this host will cause the records to be re-allocated.</li>
    332      * </ul>
    333      */
    334     public static void deleteAllHosts() {
    335         try {
    336             sService.deleteAllHosts();
    337         }
    338         catch (RemoteException e) {
    339             throw new RuntimeException("system server dead?", e);
    340         }
    341     }
    342 
    343     /**
    344      * Create the AppWidgetHostView for the given widget.
    345      * The AppWidgetHost retains a pointer to the newly-created View.
    346      */
    347     public final AppWidgetHostView createView(Context context, int appWidgetId,
    348             AppWidgetProviderInfo appWidget) {
    349         AppWidgetHostView view = onCreateView(context, appWidgetId, appWidget);
    350         view.setOnClickHandler(mOnClickHandler);
    351         view.setAppWidget(appWidgetId, appWidget);
    352         synchronized (mViews) {
    353             mViews.put(appWidgetId, view);
    354         }
    355         RemoteViews views;
    356         try {
    357             views = sService.getAppWidgetViews(mContextOpPackageName, appWidgetId);
    358         } catch (RemoteException e) {
    359             throw new RuntimeException("system server dead?", e);
    360         }
    361         view.updateAppWidget(views);
    362 
    363         return view;
    364     }
    365 
    366     /**
    367      * Called to create the AppWidgetHostView.  Override to return a custom subclass if you
    368      * need it.  {@more}
    369      */
    370     protected AppWidgetHostView onCreateView(Context context, int appWidgetId,
    371             AppWidgetProviderInfo appWidget) {
    372         return new AppWidgetHostView(context, mOnClickHandler);
    373     }
    374 
    375     /**
    376      * Called when the AppWidget provider for a AppWidget has been upgraded to a new apk.
    377      */
    378     protected void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidget) {
    379         AppWidgetHostView v;
    380 
    381         // Convert complex to dp -- we are getting the AppWidgetProviderInfo from the
    382         // AppWidgetService, which doesn't have our context, hence we need to do the
    383         // conversion here.
    384         appWidget.minWidth =
    385             TypedValue.complexToDimensionPixelSize(appWidget.minWidth, mDisplayMetrics);
    386         appWidget.minHeight =
    387             TypedValue.complexToDimensionPixelSize(appWidget.minHeight, mDisplayMetrics);
    388         appWidget.minResizeWidth =
    389             TypedValue.complexToDimensionPixelSize(appWidget.minResizeWidth, mDisplayMetrics);
    390         appWidget.minResizeHeight =
    391             TypedValue.complexToDimensionPixelSize(appWidget.minResizeHeight, mDisplayMetrics);
    392 
    393         synchronized (mViews) {
    394             v = mViews.get(appWidgetId);
    395         }
    396         if (v != null) {
    397             v.resetAppWidget(appWidget);
    398         }
    399     }
    400 
    401     /**
    402      * Called when the set of available widgets changes (ie. widget containing packages
    403      * are added, updated or removed, or widget components are enabled or disabled.)
    404      */
    405     protected void onProvidersChanged() {
    406         // Does nothing
    407     }
    408 
    409     void updateAppWidgetView(int appWidgetId, RemoteViews views) {
    410         AppWidgetHostView v;
    411         synchronized (mViews) {
    412             v = mViews.get(appWidgetId);
    413         }
    414         if (v != null) {
    415             v.updateAppWidget(views);
    416         }
    417     }
    418 
    419     void viewDataChanged(int appWidgetId, int viewId) {
    420         AppWidgetHostView v;
    421         synchronized (mViews) {
    422             v = mViews.get(appWidgetId);
    423         }
    424         if (v != null) {
    425             v.viewDataChanged(viewId);
    426         }
    427     }
    428 
    429     /**
    430      * Clear the list of Views that have been created by this AppWidgetHost.
    431      */
    432     protected void clearViews() {
    433         synchronized (mViews) {
    434             mViews.clear();
    435         }
    436     }
    437 }
    438 
    439 
    440