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