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 android.annotation.NonNull;
     20 import android.annotation.Nullable;
     21 import android.annotation.UnsupportedAppUsage;
     22 import android.app.Activity;
     23 import android.content.ActivityNotFoundException;
     24 import android.content.Context;
     25 import android.content.IntentSender;
     26 import android.content.pm.PackageManager;
     27 import android.os.Binder;
     28 import android.os.Bundle;
     29 import android.os.Handler;
     30 import android.os.IBinder;
     31 import android.os.Looper;
     32 import android.os.Message;
     33 import android.os.Process;
     34 import android.os.RemoteException;
     35 import android.os.ServiceManager;
     36 import android.util.DisplayMetrics;
     37 import android.util.SparseArray;
     38 import android.widget.RemoteViews;
     39 import android.widget.RemoteViews.OnClickHandler;
     40 
     41 import com.android.internal.R;
     42 import com.android.internal.appwidget.IAppWidgetHost;
     43 import com.android.internal.appwidget.IAppWidgetService;
     44 
     45 import java.lang.ref.WeakReference;
     46 import java.util.List;
     47 
     48 /**
     49  * AppWidgetHost provides the interaction with the AppWidget service for apps,
     50  * like the home screen, that want to embed AppWidgets in their UI.
     51  */
     52 public class AppWidgetHost {
     53 
     54     static final int HANDLE_UPDATE = 1;
     55     static final int HANDLE_PROVIDER_CHANGED = 2;
     56     static final int HANDLE_PROVIDERS_CHANGED = 3;
     57     @UnsupportedAppUsage
     58     static final int HANDLE_VIEW_DATA_CHANGED = 4;
     59 
     60     final static Object sServiceLock = new Object();
     61     @UnsupportedAppUsage
     62     static IAppWidgetService sService;
     63     static boolean sServiceInitialized = false;
     64     private DisplayMetrics mDisplayMetrics;
     65 
     66     private String mContextOpPackageName;
     67     @UnsupportedAppUsage
     68     private final Handler mHandler;
     69     private final int mHostId;
     70     private final Callbacks mCallbacks;
     71     private final SparseArray<AppWidgetHostView> mViews = new SparseArray<>();
     72     private OnClickHandler mOnClickHandler;
     73 
     74     static class Callbacks extends IAppWidgetHost.Stub {
     75         private final WeakReference<Handler> mWeakHandler;
     76 
     77         public Callbacks(Handler handler) {
     78             mWeakHandler = new WeakReference<>(handler);
     79         }
     80 
     81         public void updateAppWidget(int appWidgetId, RemoteViews views) {
     82             if (isLocalBinder() && views != null) {
     83                 views = views.clone();
     84             }
     85             Handler handler = mWeakHandler.get();
     86             if (handler == null) {
     87                 return;
     88             }
     89             Message msg = handler.obtainMessage(HANDLE_UPDATE, appWidgetId, 0, views);
     90             msg.sendToTarget();
     91         }
     92 
     93         public void providerChanged(int appWidgetId, AppWidgetProviderInfo info) {
     94             if (isLocalBinder() && info != null) {
     95                 info = info.clone();
     96             }
     97             Handler handler = mWeakHandler.get();
     98             if (handler == null) {
     99                 return;
    100             }
    101             Message msg = handler.obtainMessage(HANDLE_PROVIDER_CHANGED,
    102                     appWidgetId, 0, info);
    103             msg.sendToTarget();
    104         }
    105 
    106         public void providersChanged() {
    107             Handler handler = mWeakHandler.get();
    108             if (handler == null) {
    109                 return;
    110             }
    111             handler.obtainMessage(HANDLE_PROVIDERS_CHANGED).sendToTarget();
    112         }
    113 
    114         public void viewDataChanged(int appWidgetId, int viewId) {
    115             Handler handler = mWeakHandler.get();
    116             if (handler == null) {
    117                 return;
    118             }
    119             Message msg = handler.obtainMessage(HANDLE_VIEW_DATA_CHANGED,
    120                     appWidgetId, viewId);
    121             msg.sendToTarget();
    122         }
    123 
    124         private static boolean isLocalBinder() {
    125             return Process.myPid() == Binder.getCallingPid();
    126         }
    127     }
    128 
    129     class UpdateHandler extends Handler {
    130         public UpdateHandler(Looper looper) {
    131             super(looper);
    132         }
    133 
    134         public void handleMessage(Message msg) {
    135             switch (msg.what) {
    136                 case HANDLE_UPDATE: {
    137                     updateAppWidgetView(msg.arg1, (RemoteViews)msg.obj);
    138                     break;
    139                 }
    140                 case HANDLE_PROVIDER_CHANGED: {
    141                     onProviderChanged(msg.arg1, (AppWidgetProviderInfo)msg.obj);
    142                     break;
    143                 }
    144                 case HANDLE_PROVIDERS_CHANGED: {
    145                     onProvidersChanged();
    146                     break;
    147                 }
    148                 case HANDLE_VIEW_DATA_CHANGED: {
    149                     viewDataChanged(msg.arg1, msg.arg2);
    150                     break;
    151                 }
    152             }
    153         }
    154     }
    155 
    156     public AppWidgetHost(Context context, int hostId) {
    157         this(context, hostId, null, context.getMainLooper());
    158     }
    159 
    160     /**
    161      * @hide
    162      */
    163     @UnsupportedAppUsage
    164     public AppWidgetHost(Context context, int hostId, OnClickHandler handler, Looper looper) {
    165         mContextOpPackageName = context.getOpPackageName();
    166         mHostId = hostId;
    167         mOnClickHandler = handler;
    168         mHandler = new UpdateHandler(looper);
    169         mCallbacks = new Callbacks(mHandler);
    170         mDisplayMetrics = context.getResources().getDisplayMetrics();
    171         bindService(context);
    172     }
    173 
    174     private static void bindService(Context context) {
    175         synchronized (sServiceLock) {
    176             if (sServiceInitialized) {
    177                 return;
    178             }
    179             sServiceInitialized = true;
    180             PackageManager packageManager = context.getPackageManager();
    181             if (!packageManager.hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS)
    182                     && !context.getResources().getBoolean(R.bool.config_enableAppWidgetService)) {
    183                 return;
    184             }
    185             IBinder b = ServiceManager.getService(Context.APPWIDGET_SERVICE);
    186             sService = IAppWidgetService.Stub.asInterface(b);
    187         }
    188     }
    189 
    190     /**
    191      * Start receiving onAppWidgetChanged calls for your AppWidgets.  Call this when your activity
    192      * becomes visible, i.e. from onStart() in your Activity.
    193      */
    194     public void startListening() {
    195         if (sService == null) {
    196             return;
    197         }
    198         final int[] idsToUpdate;
    199         synchronized (mViews) {
    200             int N = mViews.size();
    201             idsToUpdate = new int[N];
    202             for (int i = 0; i < N; i++) {
    203                 idsToUpdate[i] = mViews.keyAt(i);
    204             }
    205         }
    206         List<PendingHostUpdate> updates;
    207         try {
    208             updates = sService.startListening(
    209                     mCallbacks, mContextOpPackageName, mHostId, idsToUpdate).getList();
    210         }
    211         catch (RemoteException e) {
    212             throw new RuntimeException("system server dead?", e);
    213         }
    214 
    215         int N = updates.size();
    216         for (int i = 0; i < N; i++) {
    217             PendingHostUpdate update = updates.get(i);
    218             switch (update.type) {
    219                 case PendingHostUpdate.TYPE_VIEWS_UPDATE:
    220                     updateAppWidgetView(update.appWidgetId, update.views);
    221                     break;
    222                 case PendingHostUpdate.TYPE_PROVIDER_CHANGED:
    223                     onProviderChanged(update.appWidgetId, update.widgetInfo);
    224                     break;
    225                 case PendingHostUpdate.TYPE_VIEW_DATA_CHANGED:
    226                     viewDataChanged(update.appWidgetId, update.viewId);
    227             }
    228         }
    229     }
    230 
    231     /**
    232      * Stop receiving onAppWidgetChanged calls for your AppWidgets.  Call this when your activity is
    233      * no longer visible, i.e. from onStop() in your Activity.
    234      */
    235     public void stopListening() {
    236         if (sService == null) {
    237             return;
    238         }
    239         try {
    240             sService.stopListening(mContextOpPackageName, mHostId);
    241         }
    242         catch (RemoteException e) {
    243             throw new RuntimeException("system server dead?", e);
    244         }
    245     }
    246 
    247     /**
    248      * Get a appWidgetId for a host in the calling process.
    249      *
    250      * @return a appWidgetId
    251      */
    252     public int allocateAppWidgetId() {
    253         if (sService == null) {
    254             return -1;
    255         }
    256         try {
    257             return sService.allocateAppWidgetId(mContextOpPackageName, mHostId);
    258         }
    259         catch (RemoteException e) {
    260             throw new RuntimeException("system server dead?", e);
    261         }
    262     }
    263 
    264     /**
    265      * Starts an app widget provider configure activity for result on behalf of the caller.
    266      * Use this method if the provider is in another profile as you are not allowed to start
    267      * an activity in another profile. You can optionally provide a request code that is
    268      * returned in {@link Activity#onActivityResult(int, int, android.content.Intent)} and
    269      * an options bundle to be passed to the started activity.
    270      * <p>
    271      * Note that the provided app widget has to be bound for this method to work.
    272      * </p>
    273      *
    274      * @param activity The activity from which to start the configure one.
    275      * @param appWidgetId The bound app widget whose provider's config activity to start.
    276      * @param requestCode Optional request code retuned with the result.
    277      * @param intentFlags Optional intent flags.
    278      *
    279      * @throws android.content.ActivityNotFoundException If the activity is not found.
    280      *
    281      * @see AppWidgetProviderInfo#getProfile()
    282      */
    283     public final void startAppWidgetConfigureActivityForResult(@NonNull Activity activity,
    284             int appWidgetId, int intentFlags, int requestCode, @Nullable Bundle options) {
    285         if (sService == null) {
    286             return;
    287         }
    288         try {
    289             IntentSender intentSender = sService.createAppWidgetConfigIntentSender(
    290                     mContextOpPackageName, appWidgetId, intentFlags);
    291             if (intentSender != null) {
    292                 activity.startIntentSenderForResult(intentSender, requestCode, null, 0, 0, 0,
    293                         options);
    294             } else {
    295                 throw new ActivityNotFoundException();
    296             }
    297         } catch (IntentSender.SendIntentException e) {
    298             throw new ActivityNotFoundException();
    299         } catch (RemoteException e) {
    300             throw new RuntimeException("system server dead?", e);
    301         }
    302     }
    303 
    304     /**
    305      * Gets a list of all the appWidgetIds that are bound to the current host
    306      */
    307     public int[] getAppWidgetIds() {
    308         if (sService == null) {
    309             return new int[0];
    310         }
    311         try {
    312             return sService.getAppWidgetIdsForHost(mContextOpPackageName, mHostId);
    313         } catch (RemoteException e) {
    314             throw new RuntimeException("system server dead?", e);
    315         }
    316     }
    317 
    318     /**
    319      * Stop listening to changes for this AppWidget.
    320      */
    321     public void deleteAppWidgetId(int appWidgetId) {
    322         if (sService == null) {
    323             return;
    324         }
    325         synchronized (mViews) {
    326             mViews.remove(appWidgetId);
    327             try {
    328                 sService.deleteAppWidgetId(mContextOpPackageName, appWidgetId);
    329             }
    330             catch (RemoteException e) {
    331                 throw new RuntimeException("system server dead?", e);
    332             }
    333         }
    334     }
    335 
    336     /**
    337      * Remove all records about this host from the AppWidget manager.
    338      * <ul>
    339      *   <li>Call this when initializing your database, as it might be because of a data wipe.</li>
    340      *   <li>Call this to have the AppWidget manager release all resources associated with your
    341      *   host.  Any future calls about this host will cause the records to be re-allocated.</li>
    342      * </ul>
    343      */
    344     public void deleteHost() {
    345         if (sService == null) {
    346             return;
    347         }
    348         try {
    349             sService.deleteHost(mContextOpPackageName, mHostId);
    350         }
    351         catch (RemoteException e) {
    352             throw new RuntimeException("system server dead?", e);
    353         }
    354     }
    355 
    356     /**
    357      * Remove all records about all hosts for your package.
    358      * <ul>
    359      *   <li>Call this when initializing your database, as it might be because of a data wipe.</li>
    360      *   <li>Call this to have the AppWidget manager release all resources associated with your
    361      *   host.  Any future calls about this host will cause the records to be re-allocated.</li>
    362      * </ul>
    363      */
    364     public static void deleteAllHosts() {
    365         if (sService == null) {
    366             return;
    367         }
    368         try {
    369             sService.deleteAllHosts();
    370         }
    371         catch (RemoteException e) {
    372             throw new RuntimeException("system server dead?", e);
    373         }
    374     }
    375 
    376     /**
    377      * Create the AppWidgetHostView for the given widget.
    378      * The AppWidgetHost retains a pointer to the newly-created View.
    379      */
    380     public final AppWidgetHostView createView(Context context, int appWidgetId,
    381             AppWidgetProviderInfo appWidget) {
    382         if (sService == null) {
    383             return null;
    384         }
    385         AppWidgetHostView view = onCreateView(context, appWidgetId, appWidget);
    386         view.setOnClickHandler(mOnClickHandler);
    387         view.setAppWidget(appWidgetId, appWidget);
    388         synchronized (mViews) {
    389             mViews.put(appWidgetId, view);
    390         }
    391         RemoteViews views;
    392         try {
    393             views = sService.getAppWidgetViews(mContextOpPackageName, appWidgetId);
    394         } catch (RemoteException e) {
    395             throw new RuntimeException("system server dead?", e);
    396         }
    397         view.updateAppWidget(views);
    398 
    399         return view;
    400     }
    401 
    402     /**
    403      * Called to create the AppWidgetHostView.  Override to return a custom subclass if you
    404      * need it.  {@more}
    405      */
    406     protected AppWidgetHostView onCreateView(Context context, int appWidgetId,
    407             AppWidgetProviderInfo appWidget) {
    408         return new AppWidgetHostView(context, mOnClickHandler);
    409     }
    410 
    411     /**
    412      * Called when the AppWidget provider for a AppWidget has been upgraded to a new apk.
    413      */
    414     protected void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidget) {
    415         AppWidgetHostView v;
    416 
    417         // Convert complex to dp -- we are getting the AppWidgetProviderInfo from the
    418         // AppWidgetService, which doesn't have our context, hence we need to do the
    419         // conversion here.
    420         appWidget.updateDimensions(mDisplayMetrics);
    421         synchronized (mViews) {
    422             v = mViews.get(appWidgetId);
    423         }
    424         if (v != null) {
    425             v.resetAppWidget(appWidget);
    426         }
    427     }
    428 
    429     /**
    430      * Called when the set of available widgets changes (ie. widget containing packages
    431      * are added, updated or removed, or widget components are enabled or disabled.)
    432      */
    433     protected void onProvidersChanged() {
    434         // Does nothing
    435     }
    436 
    437     void updateAppWidgetView(int appWidgetId, RemoteViews views) {
    438         AppWidgetHostView v;
    439         synchronized (mViews) {
    440             v = mViews.get(appWidgetId);
    441         }
    442         if (v != null) {
    443             v.updateAppWidget(views);
    444         }
    445     }
    446 
    447     void viewDataChanged(int appWidgetId, int viewId) {
    448         AppWidgetHostView v;
    449         synchronized (mViews) {
    450             v = mViews.get(appWidgetId);
    451         }
    452         if (v != null) {
    453             v.viewDataChanged(viewId);
    454         }
    455     }
    456 
    457     /**
    458      * Clear the list of Views that have been created by this AppWidgetHost.
    459      */
    460     protected void clearViews() {
    461         synchronized (mViews) {
    462             mViews.clear();
    463         }
    464     }
    465 }
    466 
    467 
    468