| /* |
| * Copyright (C) 2009 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.appwidget; |
| |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.app.Activity; |
| import android.compat.annotation.UnsupportedAppUsage; |
| import android.content.ActivityNotFoundException; |
| import android.content.Context; |
| import android.content.IntentSender; |
| import android.content.pm.PackageManager; |
| import android.os.Binder; |
| import android.os.Build; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.os.IBinder; |
| import android.os.Looper; |
| import android.os.Message; |
| import android.os.Process; |
| import android.os.RemoteException; |
| import android.os.ServiceManager; |
| import android.util.DisplayMetrics; |
| import android.util.Log; |
| import android.util.SparseArray; |
| import android.widget.RemoteViews; |
| import android.widget.RemoteViews.InteractionHandler; |
| |
| import com.android.internal.R; |
| import com.android.internal.appwidget.IAppWidgetHost; |
| import com.android.internal.appwidget.IAppWidgetService; |
| |
| import java.lang.ref.WeakReference; |
| import java.util.List; |
| |
| /** |
| * AppWidgetHost provides the interaction with the AppWidget service for apps, |
| * like the home screen, that want to embed AppWidgets in their UI. |
| */ |
| public class AppWidgetHost { |
| |
| private static final String TAG = "AppWidgetHost"; |
| |
| static final int HANDLE_UPDATE = 1; |
| static final int HANDLE_PROVIDER_CHANGED = 2; |
| static final int HANDLE_PROVIDERS_CHANGED = 3; |
| @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) |
| static final int HANDLE_VIEW_DATA_CHANGED = 4; |
| static final int HANDLE_APP_WIDGET_REMOVED = 5; |
| static final int HANDLE_VIEW_UPDATE_DEFERRED = 6; |
| |
| final static Object sServiceLock = new Object(); |
| @UnsupportedAppUsage |
| static IAppWidgetService sService; |
| static boolean sServiceInitialized = false; |
| private DisplayMetrics mDisplayMetrics; |
| |
| private String mContextOpPackageName; |
| @UnsupportedAppUsage |
| private final Handler mHandler; |
| private final int mHostId; |
| private final Callbacks mCallbacks; |
| private final SparseArray<AppWidgetHostListener> mListeners = new SparseArray<>(); |
| private InteractionHandler mInteractionHandler; |
| |
| static class Callbacks extends IAppWidgetHost.Stub { |
| private final WeakReference<Handler> mWeakHandler; |
| |
| public Callbacks(Handler handler) { |
| mWeakHandler = new WeakReference<>(handler); |
| } |
| |
| public void updateAppWidget(int appWidgetId, RemoteViews views) { |
| if (isLocalBinder() && views != null) { |
| views = views.clone(); |
| } |
| Handler handler = mWeakHandler.get(); |
| if (handler == null) { |
| return; |
| } |
| Message msg = handler.obtainMessage(HANDLE_UPDATE, appWidgetId, 0, views); |
| msg.sendToTarget(); |
| } |
| |
| public void providerChanged(int appWidgetId, AppWidgetProviderInfo info) { |
| if (isLocalBinder() && info != null) { |
| info = info.clone(); |
| } |
| Handler handler = mWeakHandler.get(); |
| if (handler == null) { |
| return; |
| } |
| Message msg = handler.obtainMessage(HANDLE_PROVIDER_CHANGED, |
| appWidgetId, 0, info); |
| msg.sendToTarget(); |
| } |
| |
| public void appWidgetRemoved(int appWidgetId) { |
| Handler handler = mWeakHandler.get(); |
| if (handler == null) { |
| return; |
| } |
| handler.obtainMessage(HANDLE_APP_WIDGET_REMOVED, appWidgetId, 0).sendToTarget(); |
| } |
| |
| public void providersChanged() { |
| Handler handler = mWeakHandler.get(); |
| if (handler == null) { |
| return; |
| } |
| handler.obtainMessage(HANDLE_PROVIDERS_CHANGED).sendToTarget(); |
| } |
| |
| public void viewDataChanged(int appWidgetId, int viewId) { |
| Handler handler = mWeakHandler.get(); |
| if (handler == null) { |
| return; |
| } |
| Message msg = handler.obtainMessage(HANDLE_VIEW_DATA_CHANGED, |
| appWidgetId, viewId); |
| msg.sendToTarget(); |
| } |
| |
| public void updateAppWidgetDeferred(int appWidgetId) { |
| Handler handler = mWeakHandler.get(); |
| if (handler == null) { |
| return; |
| } |
| Message msg = handler.obtainMessage(HANDLE_VIEW_UPDATE_DEFERRED, appWidgetId, 0, null); |
| msg.sendToTarget(); |
| } |
| |
| private static boolean isLocalBinder() { |
| return Process.myPid() == Binder.getCallingPid(); |
| } |
| } |
| |
| class UpdateHandler extends Handler { |
| public UpdateHandler(Looper looper) { |
| super(looper); |
| } |
| |
| public void handleMessage(Message msg) { |
| switch (msg.what) { |
| case HANDLE_UPDATE: { |
| updateAppWidgetView(msg.arg1, (RemoteViews)msg.obj); |
| break; |
| } |
| case HANDLE_APP_WIDGET_REMOVED: { |
| dispatchOnAppWidgetRemoved(msg.arg1); |
| break; |
| } |
| case HANDLE_PROVIDER_CHANGED: { |
| onProviderChanged(msg.arg1, (AppWidgetProviderInfo)msg.obj); |
| break; |
| } |
| case HANDLE_PROVIDERS_CHANGED: { |
| onProvidersChanged(); |
| break; |
| } |
| case HANDLE_VIEW_DATA_CHANGED: { |
| viewDataChanged(msg.arg1, msg.arg2); |
| break; |
| } |
| case HANDLE_VIEW_UPDATE_DEFERRED: { |
| updateAppWidgetDeferred(msg.arg1); |
| break; |
| } |
| } |
| } |
| } |
| |
| public AppWidgetHost(Context context, int hostId) { |
| this(context, hostId, null, context.getMainLooper()); |
| } |
| |
| @Nullable |
| private AppWidgetHostListener getListener(final int appWidgetId) { |
| AppWidgetHostListener tempListener = null; |
| synchronized (mListeners) { |
| tempListener = mListeners.get(appWidgetId); |
| } |
| return tempListener; |
| } |
| |
| /** |
| * @hide |
| */ |
| @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) |
| public AppWidgetHost(Context context, int hostId, InteractionHandler handler, Looper looper) { |
| mContextOpPackageName = context.getOpPackageName(); |
| mHostId = hostId; |
| mInteractionHandler = handler; |
| mHandler = new UpdateHandler(looper); |
| mCallbacks = new Callbacks(mHandler); |
| mDisplayMetrics = context.getResources().getDisplayMetrics(); |
| bindService(context); |
| } |
| |
| private static void bindService(Context context) { |
| synchronized (sServiceLock) { |
| if (sServiceInitialized) { |
| return; |
| } |
| sServiceInitialized = true; |
| PackageManager packageManager = context.getPackageManager(); |
| if (!packageManager.hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS) |
| && !context.getResources().getBoolean(R.bool.config_enableAppWidgetService)) { |
| return; |
| } |
| IBinder b = ServiceManager.getService(Context.APPWIDGET_SERVICE); |
| sService = IAppWidgetService.Stub.asInterface(b); |
| } |
| } |
| |
| /** |
| * Start receiving onAppWidgetChanged calls for your AppWidgets. Call this when your activity |
| * becomes visible, i.e. from onStart() in your Activity. |
| */ |
| public void startListening() { |
| if (sService == null) { |
| return; |
| } |
| final int[] idsToUpdate; |
| synchronized (mListeners) { |
| int n = mListeners.size(); |
| idsToUpdate = new int[n]; |
| for (int i = 0; i < n; i++) { |
| idsToUpdate[i] = mListeners.keyAt(i); |
| } |
| } |
| List<PendingHostUpdate> updates; |
| try { |
| updates = sService.startListening( |
| mCallbacks, mContextOpPackageName, mHostId, idsToUpdate).getList(); |
| } |
| catch (RemoteException e) { |
| throw new RuntimeException("system server dead?", e); |
| } |
| |
| int N = updates.size(); |
| for (int i = 0; i < N; i++) { |
| PendingHostUpdate update = updates.get(i); |
| switch (update.type) { |
| case PendingHostUpdate.TYPE_VIEWS_UPDATE: |
| updateAppWidgetView(update.appWidgetId, update.views); |
| break; |
| case PendingHostUpdate.TYPE_PROVIDER_CHANGED: |
| onProviderChanged(update.appWidgetId, update.widgetInfo); |
| break; |
| case PendingHostUpdate.TYPE_VIEW_DATA_CHANGED: |
| viewDataChanged(update.appWidgetId, update.viewId); |
| break; |
| case PendingHostUpdate.TYPE_APP_WIDGET_REMOVED: |
| dispatchOnAppWidgetRemoved(update.appWidgetId); |
| break; |
| } |
| } |
| } |
| |
| /** |
| * Stop receiving onAppWidgetChanged calls for your AppWidgets. Call this when your activity is |
| * no longer visible, i.e. from onStop() in your Activity. |
| */ |
| public void stopListening() { |
| if (sService == null) { |
| return; |
| } |
| try { |
| sService.stopListening(mContextOpPackageName, mHostId); |
| } |
| catch (RemoteException e) { |
| throw new RuntimeException("system server dead?", e); |
| } |
| } |
| |
| /** |
| * Get a appWidgetId for a host in the calling process. |
| * |
| * @return a appWidgetId |
| */ |
| public int allocateAppWidgetId() { |
| if (sService == null) { |
| return -1; |
| } |
| try { |
| return sService.allocateAppWidgetId(mContextOpPackageName, mHostId); |
| } |
| catch (RemoteException e) { |
| throw new RuntimeException("system server dead?", e); |
| } |
| } |
| |
| /** |
| * Starts an app widget provider configure activity for result on behalf of the caller. |
| * Use this method if the provider is in another profile as you are not allowed to start |
| * an activity in another profile. You can optionally provide a request code that is |
| * returned in {@link Activity#onActivityResult(int, int, android.content.Intent)} and |
| * an options bundle to be passed to the started activity. |
| * <p> |
| * Note that the provided app widget has to be bound for this method to work. |
| * </p> |
| * |
| * @param activity The activity from which to start the configure one. |
| * @param appWidgetId The bound app widget whose provider's config activity to start. |
| * @param requestCode Optional request code retuned with the result. |
| * @param intentFlags Optional intent flags. |
| * |
| * @throws android.content.ActivityNotFoundException If the activity is not found. |
| * |
| * @see AppWidgetProviderInfo#getProfile() |
| */ |
| public final void startAppWidgetConfigureActivityForResult(@NonNull Activity activity, |
| int appWidgetId, int intentFlags, int requestCode, @Nullable Bundle options) { |
| if (sService == null) { |
| return; |
| } |
| try { |
| IntentSender intentSender = sService.createAppWidgetConfigIntentSender( |
| mContextOpPackageName, appWidgetId, intentFlags); |
| if (intentSender != null) { |
| activity.startIntentSenderForResult(intentSender, requestCode, null, 0, 0, 0, |
| options); |
| } else { |
| throw new ActivityNotFoundException(); |
| } |
| } catch (IntentSender.SendIntentException e) { |
| throw new ActivityNotFoundException(); |
| } catch (RemoteException e) { |
| throw new RuntimeException("system server dead?", e); |
| } |
| } |
| |
| /** |
| * Set the visibiity of all widgets associated with this host to hidden |
| * |
| * @hide |
| */ |
| public void setAppWidgetHidden() { |
| if (sService == null) { |
| return; |
| } |
| try { |
| sService.setAppWidgetHidden(mContextOpPackageName, mHostId); |
| } catch (RemoteException e) { |
| throw new RuntimeException("System server dead?", e); |
| } |
| } |
| |
| /** |
| * Set the host's interaction handler. |
| * |
| * @hide |
| */ |
| public void setInteractionHandler(InteractionHandler interactionHandler) { |
| mInteractionHandler = interactionHandler; |
| } |
| |
| /** |
| * Gets a list of all the appWidgetIds that are bound to the current host |
| */ |
| public int[] getAppWidgetIds() { |
| if (sService == null) { |
| return new int[0]; |
| } |
| try { |
| return sService.getAppWidgetIdsForHost(mContextOpPackageName, mHostId); |
| } catch (RemoteException e) { |
| throw new RuntimeException("system server dead?", e); |
| } |
| } |
| |
| /** |
| * Stop listening to changes for this AppWidget. |
| */ |
| public void deleteAppWidgetId(int appWidgetId) { |
| if (sService == null) { |
| return; |
| } |
| removeListener(appWidgetId); |
| try { |
| sService.deleteAppWidgetId(mContextOpPackageName, appWidgetId); |
| } catch (RemoteException e) { |
| throw new RuntimeException("system server dead?", e); |
| } |
| } |
| |
| /** |
| * Remove all records about this host from the AppWidget manager. |
| * <ul> |
| * <li>Call this when initializing your database, as it might be because of a data wipe.</li> |
| * <li>Call this to have the AppWidget manager release all resources associated with your |
| * host. Any future calls about this host will cause the records to be re-allocated.</li> |
| * </ul> |
| */ |
| public void deleteHost() { |
| if (sService == null) { |
| return; |
| } |
| try { |
| sService.deleteHost(mContextOpPackageName, mHostId); |
| } |
| catch (RemoteException e) { |
| throw new RuntimeException("system server dead?", e); |
| } |
| } |
| |
| /** |
| * Remove all records about all hosts for your package. |
| * <ul> |
| * <li>Call this when initializing your database, as it might be because of a data wipe.</li> |
| * <li>Call this to have the AppWidget manager release all resources associated with your |
| * host. Any future calls about this host will cause the records to be re-allocated.</li> |
| * </ul> |
| */ |
| public static void deleteAllHosts() { |
| if (sService == null) { |
| return; |
| } |
| try { |
| sService.deleteAllHosts(); |
| } |
| catch (RemoteException e) { |
| throw new RuntimeException("system server dead?", e); |
| } |
| } |
| |
| /** |
| * Create the AppWidgetHostView for the given widget. |
| * The AppWidgetHost retains a pointer to the newly-created View. |
| */ |
| public final AppWidgetHostView createView(Context context, int appWidgetId, |
| AppWidgetProviderInfo appWidget) { |
| if (sService == null) { |
| return null; |
| } |
| AppWidgetHostView view = onCreateView(context, appWidgetId, appWidget); |
| view.setInteractionHandler(mInteractionHandler); |
| view.setAppWidget(appWidgetId, appWidget); |
| setListener(appWidgetId, view); |
| |
| return view; |
| } |
| |
| /** |
| * Called to create the AppWidgetHostView. Override to return a custom subclass if you |
| * need it. {@more} |
| */ |
| protected AppWidgetHostView onCreateView(Context context, int appWidgetId, |
| AppWidgetProviderInfo appWidget) { |
| return new AppWidgetHostView(context, mInteractionHandler); |
| } |
| |
| /** |
| * Called when the AppWidget provider for a AppWidget has been upgraded to a new apk. |
| */ |
| protected void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidget) { |
| AppWidgetHostListener v = getListener(appWidgetId); |
| |
| // Convert complex to dp -- we are getting the AppWidgetProviderInfo from the |
| // AppWidgetService, which doesn't have our context, hence we need to do the |
| // conversion here. |
| appWidget.updateDimensions(mDisplayMetrics); |
| if (v != null) { |
| v.onUpdateProviderInfo(appWidget); |
| } |
| } |
| |
| /** |
| * This interface specifies the actions to be performed on the app widget based on the calls |
| * from the service |
| * |
| * @hide |
| */ |
| public interface AppWidgetHostListener { |
| |
| /** |
| * This function is called when the service want to reset the app widget provider info |
| * @param appWidget The new app widget provider info |
| * |
| * @hide |
| */ |
| void onUpdateProviderInfo(@Nullable AppWidgetProviderInfo appWidget); |
| |
| /** |
| * This function is called when the {@code RemoteViews} of the app widget is updated |
| * @param views The new {@code RemoteViews} to be set for the app widget |
| * |
| * @hide |
| */ |
| void updateAppWidget(@Nullable RemoteViews views); |
| |
| /** |
| * Called for the listener to handle deferred {@code RemoteViews} updates. Default |
| * implementation is to update the widget directly. |
| * @param packageName The package name used for uid verification on the service side |
| * @param appWidgetId The widget id of the listener |
| * |
| * @hide |
| */ |
| default void updateAppWidgetDeferred(String packageName, int appWidgetId) { |
| RemoteViews latestViews = null; |
| try { |
| latestViews = sService.getAppWidgetViews(packageName, appWidgetId); |
| } catch (Exception e) { |
| Log.e(TAG, "updateAppWidgetDeferred: ", e); |
| } |
| updateAppWidget(latestViews); |
| } |
| |
| /** |
| * This function is called when the view ID is changed for the app widget |
| * @param viewId The new view ID to be be set for the widget |
| * |
| * @hide |
| */ |
| void onViewDataChanged(int viewId); |
| } |
| |
| void dispatchOnAppWidgetRemoved(int appWidgetId) { |
| removeListener(appWidgetId); |
| onAppWidgetRemoved(appWidgetId); |
| } |
| |
| /** |
| * Called when the app widget is removed for appWidgetId |
| * @param appWidgetId |
| */ |
| public void onAppWidgetRemoved(int appWidgetId) { |
| // Does nothing |
| } |
| |
| /** |
| * Called when the set of available widgets changes (ie. widget containing packages |
| * are added, updated or removed, or widget components are enabled or disabled.) |
| */ |
| protected void onProvidersChanged() { |
| // Does nothing |
| } |
| |
| /** |
| * Create an AppWidgetHostListener for the given widget. |
| * The AppWidgetHost retains a pointer to the newly-created listener. |
| * @param appWidgetId The ID of the app widget for which to add the listener |
| * @param listener The listener interface that deals with actions towards the widget view |
| * @hide |
| */ |
| public void setListener(int appWidgetId, @NonNull AppWidgetHostListener listener) { |
| synchronized (mListeners) { |
| mListeners.put(appWidgetId, listener); |
| } |
| RemoteViews views = null; |
| try { |
| views = sService.getAppWidgetViews(mContextOpPackageName, appWidgetId); |
| } catch (RemoteException e) { |
| throw new RuntimeException("system server dead?", e); |
| } |
| listener.updateAppWidget(views); |
| } |
| |
| /** |
| * Delete the listener for the given widget |
| * @param appWidgetId The ID of the app widget for which the listener is to be deleted |
| |
| * @hide |
| */ |
| public void removeListener(int appWidgetId) { |
| synchronized (mListeners) { |
| mListeners.remove(appWidgetId); |
| } |
| } |
| |
| void updateAppWidgetView(int appWidgetId, RemoteViews views) { |
| AppWidgetHostListener v = getListener(appWidgetId); |
| if (v != null) { |
| v.updateAppWidget(views); |
| } |
| } |
| |
| void viewDataChanged(int appWidgetId, int viewId) { |
| AppWidgetHostListener v = getListener(appWidgetId); |
| if (v != null) { |
| v.onViewDataChanged(viewId); |
| } |
| } |
| |
| private void updateAppWidgetDeferred(int appWidgetId) { |
| AppWidgetHostListener v = getListener(appWidgetId); |
| if (v == null) { |
| Log.e(TAG, "updateAppWidgetDeferred: null listener for id: " + appWidgetId); |
| return; |
| } |
| v.updateAppWidgetDeferred(mContextOpPackageName, appWidgetId); |
| } |
| |
| /** |
| * Clear the list of Views that have been created by this AppWidgetHost. |
| */ |
| protected void clearViews() { |
| synchronized (mListeners) { |
| mListeners.clear(); |
| } |
| } |
| } |
| |
| |