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