diff --git a/core/java/android/appwidget/AppWidgetHost.java b/core/java/android/appwidget/AppWidgetHost.java index 2d9f4a71b0054..cd144695a989d 100644 --- a/core/java/android/appwidget/AppWidgetHost.java +++ b/core/java/android/appwidget/AppWidgetHost.java @@ -34,7 +34,6 @@ import android.os.Message; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; -import android.os.SystemClock; import android.util.DisplayMetrics; import android.util.SparseArray; import android.util.TypedValue; @@ -187,19 +186,28 @@ public class AppWidgetHost { idsToUpdate[i] = mViews.keyAt(i); } } - List updatedViews; - int[] updatedIds = new int[idsToUpdate.length]; + List updates; try { - updatedViews = sService.startListening( - mCallbacks, mContextOpPackageName, mHostId, idsToUpdate, updatedIds).getList(); + updates = sService.startListening( + mCallbacks, mContextOpPackageName, mHostId, idsToUpdate).getList(); } catch (RemoteException e) { throw new RuntimeException("system server dead?", e); } - int N = updatedViews.size(); + int N = updates.size(); for (int i = 0; i < N; i++) { - updateAppWidgetView(updatedIds[i], updatedViews.get(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); + } } } diff --git a/core/java/android/appwidget/PendingHostUpdate.java b/core/java/android/appwidget/PendingHostUpdate.java new file mode 100644 index 0000000000000..578031908f52e --- /dev/null +++ b/core/java/android/appwidget/PendingHostUpdate.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2016 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.os.Parcel; +import android.os.Parcelable; +import android.widget.RemoteViews; + +/** + * @hide + */ +public class PendingHostUpdate implements Parcelable { + + static final int TYPE_VIEWS_UPDATE = 0; + static final int TYPE_PROVIDER_CHANGED = 1; + static final int TYPE_VIEW_DATA_CHANGED = 2; + + final int appWidgetId; + final int type; + RemoteViews views; + AppWidgetProviderInfo widgetInfo; + int viewId; + + public static PendingHostUpdate updateAppWidget(int appWidgetId, RemoteViews views) { + PendingHostUpdate update = new PendingHostUpdate(appWidgetId, TYPE_VIEWS_UPDATE); + update.views = views; + return update; + } + + public static PendingHostUpdate providerChanged(int appWidgetId, AppWidgetProviderInfo info) { + PendingHostUpdate update = new PendingHostUpdate(appWidgetId, TYPE_PROVIDER_CHANGED); + update.widgetInfo = info; + return update; + } + + public static PendingHostUpdate viewDataChanged(int appWidgetId, int viewId) { + PendingHostUpdate update = new PendingHostUpdate(appWidgetId, TYPE_VIEW_DATA_CHANGED); + update.viewId = viewId; + return update; + } + + private PendingHostUpdate(int appWidgetId, int type) { + this.appWidgetId = appWidgetId; + this.type = type; + } + + private PendingHostUpdate(Parcel in) { + appWidgetId = in.readInt(); + type = in.readInt(); + + switch (type) { + case TYPE_VIEWS_UPDATE: + if (0 != in.readInt()) { + views = new RemoteViews(in); + } + break; + case TYPE_PROVIDER_CHANGED: + if (0 != in.readInt()) { + widgetInfo = new AppWidgetProviderInfo(in); + } + break; + case TYPE_VIEW_DATA_CHANGED: + viewId = in.readInt(); + } + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(appWidgetId); + dest.writeInt(type); + switch (type) { + case TYPE_VIEWS_UPDATE: + writeNullParcelable(views, dest, flags); + break; + case TYPE_PROVIDER_CHANGED: + writeNullParcelable(widgetInfo, dest, flags); + break; + case TYPE_VIEW_DATA_CHANGED: + dest.writeInt(viewId); + break; + } + } + + private void writeNullParcelable(Parcelable p, Parcel dest, int flags) { + if (p != null) { + dest.writeInt(1); + p.writeToParcel(dest, flags); + } else { + dest.writeInt(0); + } + } + + /** + * Parcelable.Creator that instantiates PendingHostUpdate objects + */ + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + public PendingHostUpdate createFromParcel(Parcel parcel) { + return new PendingHostUpdate(parcel); + } + + public PendingHostUpdate[] newArray(int size) { + return new PendingHostUpdate[size]; + } + }; +} diff --git a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl index 4260e50a31f26..951a45a7b4376 100644 --- a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl +++ b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl @@ -34,7 +34,7 @@ interface IAppWidgetService { // for AppWidgetHost // ParceledListSlice startListening(IAppWidgetHost host, String callingPackage, int hostId, - in int[] appWidgetIds, out int[] updatedIds); + in int[] appWidgetIds); void stopListening(String callingPackage, int hostId); int allocateAppWidgetId(String callingPackage, int hostId); void deleteAppWidgetId(String callingPackage, int appWidgetId); diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index ac2dda3492cab..5341621406b72 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -29,6 +29,7 @@ import android.app.admin.DevicePolicyManagerInternal; import android.app.admin.DevicePolicyManagerInternal.OnCrossProfileWidgetProvidersChangeListener; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProviderInfo; +import android.appwidget.PendingHostUpdate; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -70,10 +71,12 @@ import android.text.TextUtils; import android.util.ArraySet; import android.util.AtomicFile; import android.util.AttributeSet; +import android.util.LongSparseArray; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; +import android.util.SparseLongArray; import android.util.TypedValue; import android.util.Xml; import android.view.Display; @@ -745,8 +748,8 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } @Override - public ParceledListSlice startListening(IAppWidgetHost callbacks, - String callingPackage, int hostId, int[] appWidgetIds, int[] updatedIds) { + public ParceledListSlice startListening(IAppWidgetHost callbacks, + String callingPackage, int hostId, int[] appWidgetIds) { final int userId = UserHandle.getCallingUserId(); if (DEBUG) { @@ -766,18 +769,19 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku host.callbacks = callbacks; int N = appWidgetIds.length; - ArrayList outViews = new ArrayList<>(N); - RemoteViews rv; - int added = 0; + ArrayList outUpdates = new ArrayList<>(N); + + LongSparseArray updatesMap = new LongSparseArray<>(); for (int i = 0; i < N; i++) { - rv = host.getPendingViewsForId(appWidgetIds[i]); - if (rv != null) { - updatedIds[added] = appWidgetIds[i]; - outViews.add(rv); - added++; + if (host.getPendingUpdatesForId(appWidgetIds[i], updatesMap)) { + // We key the updates based on time, so that the values are sorted by time. + int M = updatesMap.size(); + for (int j = 0; j < M; j++) { + outUpdates.add(updatesMap.valueAt(j)); + } } } - return new ParceledListSlice<>(outViews); + return new ParceledListSlice<>(outUpdates); } } @@ -1817,6 +1821,15 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } private void scheduleNotifyAppWidgetViewDataChanged(Widget widget, int viewId) { + if (viewId == ID_VIEWS_UPDATE || viewId == ID_PROVIDER_CHANGED) { + // A view id should never collide with these constants but a developer can call this + // method with a wrong id. In that case, ignore the call. + return; + } + long requestTime = SystemClock.uptimeMillis(); + if (widget != null) { + widget.updateTimes.put(viewId, requestTime); + } if (widget == null || widget.host == null || widget.host.zombie || widget.host.callbacks == null || widget.provider == null || widget.provider.zombie) { @@ -1826,6 +1839,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku SomeArgs args = SomeArgs.obtain(); args.arg1 = widget.host; args.arg2 = widget.host.callbacks; + args.arg3 = requestTime; args.argi1 = widget.appWidgetId; args.argi2 = viewId; @@ -1836,9 +1850,10 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku private void handleNotifyAppWidgetViewDataChanged(Host host, IAppWidgetHost callbacks, - int appWidgetId, int viewId) { + int appWidgetId, int viewId, long requestTime) { try { callbacks.viewDataChanged(appWidgetId, viewId); + host.lastWidgetUpdateTime = requestTime; } catch (RemoteException re) { // It failed; remove the callback. No need to prune because // we know that this host is still referenced by this instance. @@ -1887,7 +1902,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku private void scheduleNotifyUpdateAppWidgetLocked(Widget widget, RemoteViews updateViews) { long requestTime = SystemClock.uptimeMillis(); if (widget != null) { - widget.lastUpdateTime = requestTime; + widget.updateTimes.put(ID_VIEWS_UPDATE, requestTime); } if (widget == null || widget.provider == null || widget.provider.zombie || widget.host.callbacks == null || widget.host.zombie) { @@ -1920,6 +1935,12 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } private void scheduleNotifyProviderChangedLocked(Widget widget) { + long requestTime = SystemClock.uptimeMillis(); + if (widget != null) { + // When the provider changes, reset everything else. + widget.updateTimes.clear(); + widget.updateTimes.append(ID_PROVIDER_CHANGED, requestTime); + } if (widget == null || widget.provider == null || widget.provider.zombie || widget.host.callbacks == null || widget.host.zombie) { return; @@ -1929,6 +1950,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku args.arg1 = widget.host; args.arg2 = widget.host.callbacks; args.arg3 = widget.provider.info; + args.arg4 = requestTime; args.argi1 = widget.appWidgetId; mCallbackHandler.obtainMessage( @@ -1937,9 +1959,10 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } private void handleNotifyProviderChanged(Host host, IAppWidgetHost callbacks, - int appWidgetId, AppWidgetProviderInfo info) { + int appWidgetId, AppWidgetProviderInfo info, long requestTime) { try { callbacks.providerChanged(appWidgetId, info); + host.lastWidgetUpdateTime = requestTime; } catch (RemoteException re) { synchronized (mLock){ Slog.e(TAG, "Widget host dead: " + host.id, re); @@ -3423,10 +3446,11 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku Host host = (Host) args.arg1; IAppWidgetHost callbacks = (IAppWidgetHost) args.arg2; AppWidgetProviderInfo info = (AppWidgetProviderInfo)args.arg3; + long requestTime = (Long) args.arg4; final int appWidgetId = args.argi1; args.recycle(); - handleNotifyProviderChanged(host, callbacks, appWidgetId, info); + handleNotifyProviderChanged(host, callbacks, appWidgetId, info, requestTime); } break; case MSG_NOTIFY_PROVIDERS_CHANGED: { @@ -3442,11 +3466,13 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku SomeArgs args = (SomeArgs) message.obj; Host host = (Host) args.arg1; IAppWidgetHost callbacks = (IAppWidgetHost) args.arg2; + long requestTime = (Long) args.arg3; final int appWidgetId = args.argi1; final int viewId = args.argi2; args.recycle(); - handleNotifyAppWidgetViewDataChanged(host, callbacks, appWidgetId, viewId); + handleNotifyAppWidgetViewDataChanged(host, callbacks, appWidgetId, viewId, + requestTime); } break; } } @@ -3785,20 +3811,41 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } /** - * Returns the RemoveViews for the provided widget id if an update is pending - * for that widget. + * Adds all pending updates in {@param outUpdates} keys by the update time. */ - public RemoteViews getPendingViewsForId(int appWidgetId) { + public boolean getPendingUpdatesForId(int appWidgetId, + LongSparseArray outUpdates) { long updateTime = lastWidgetUpdateTime; int N = widgets.size(); for (int i = 0; i < N; i++) { Widget widget = widgets.get(i); - if (widget.appWidgetId == appWidgetId - && widget.lastUpdateTime > updateTime) { - return cloneIfLocalBinder(widget.getEffectiveViewsLocked()); + if (widget.appWidgetId == appWidgetId) { + outUpdates.clear(); + for (int j = widget.updateTimes.size() - 1; j >= 0; j--) { + long time = widget.updateTimes.valueAt(j); + if (time <= updateTime) { + continue; + } + int id = widget.updateTimes.keyAt(j); + final PendingHostUpdate update; + switch (id) { + case ID_PROVIDER_CHANGED: + update = PendingHostUpdate.providerChanged( + appWidgetId, widget.provider.info); + break; + case ID_VIEWS_UPDATE: + update = PendingHostUpdate.updateAppWidget(appWidgetId, + cloneIfLocalBinder(widget.getEffectiveViewsLocked())); + break; + default: + update = PendingHostUpdate.viewDataChanged(appWidgetId, id); + } + outUpdates.put(time, update); + } + return true; } } - return null; + return false; } @Override @@ -3863,6 +3910,10 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } } + // These can be any constants that would not collide with a resource id. + private static final int ID_VIEWS_UPDATE = 0; + private static final int ID_PROVIDER_CHANGED = 1; + private static final class Widget { int appWidgetId; int restoredId; // tracking & remapping any restored state @@ -3871,7 +3922,8 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku RemoteViews maskedViews; Bundle options; Host host; - long lastUpdateTime; + // timestamps for various operations + SparseLongArray updateTimes = new SparseLongArray(2); @Override public String toString() {