Merge "Save/restore view states when reapplying RemoteViews for color changes" into sc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
6d82bd39e7
@@ -17,6 +17,7 @@
|
||||
package android.appwidget;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.Activity;
|
||||
import android.app.ActivityOptions;
|
||||
import android.compat.annotation.UnsupportedAppUsage;
|
||||
@@ -68,6 +69,7 @@ public class AppWidgetHostView extends FrameLayout {
|
||||
|
||||
static final String TAG = "AppWidgetHostView";
|
||||
private static final String KEY_JAILED_ARRAY = "jail";
|
||||
private static final String KEY_INFLATION_ID = "inflation_id";
|
||||
|
||||
static final boolean LOGD = false;
|
||||
|
||||
@@ -97,9 +99,12 @@ public class AppWidgetHostView extends FrameLayout {
|
||||
private RemoteViews.ColorResources mColorResources = null;
|
||||
// Stores the last remote views last inflated.
|
||||
private RemoteViews mLastInflatedRemoteViews = null;
|
||||
private long mLastInflatedRemoteViewsId = -1;
|
||||
|
||||
private Executor mAsyncExecutor;
|
||||
private CancellationSignal mLastExecutionSignal;
|
||||
private SparseArray<Parcelable> mDelayedRestoredState;
|
||||
private long mDelayedRestoredInflationId;
|
||||
|
||||
/**
|
||||
* Create a host view. Uses default fade animations.
|
||||
@@ -226,6 +231,8 @@ public class AppWidgetHostView extends FrameLayout {
|
||||
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putSparseParcelableArray(KEY_JAILED_ARRAY, jail);
|
||||
bundle.putLong(KEY_INFLATION_ID, mLastInflatedRemoteViewsId);
|
||||
container.put(generateId(), bundle);
|
||||
container.put(generateId(), bundle);
|
||||
}
|
||||
|
||||
@@ -239,14 +246,30 @@ public class AppWidgetHostView extends FrameLayout {
|
||||
final Parcelable parcelable = container.get(generateId());
|
||||
|
||||
SparseArray<Parcelable> jail = null;
|
||||
long inflationId = -1;
|
||||
if (parcelable instanceof Bundle) {
|
||||
jail = ((Bundle) parcelable).getSparseParcelableArray(KEY_JAILED_ARRAY);
|
||||
Bundle bundle = (Bundle) parcelable;
|
||||
jail = bundle.getSparseParcelableArray(KEY_JAILED_ARRAY);
|
||||
inflationId = bundle.getLong(KEY_INFLATION_ID, -1);
|
||||
}
|
||||
|
||||
if (jail == null) jail = new SparseArray<>();
|
||||
|
||||
mDelayedRestoredState = jail;
|
||||
mDelayedRestoredInflationId = inflationId;
|
||||
restoreInstanceState();
|
||||
}
|
||||
|
||||
void restoreInstanceState() {
|
||||
long inflationId = mDelayedRestoredInflationId;
|
||||
SparseArray<Parcelable> state = mDelayedRestoredState;
|
||||
if (inflationId == -1 || inflationId != mLastInflatedRemoteViewsId) {
|
||||
return; // We don't restore.
|
||||
}
|
||||
mDelayedRestoredInflationId = -1;
|
||||
mDelayedRestoredState = null;
|
||||
try {
|
||||
super.dispatchRestoreInstanceState(jail);
|
||||
super.dispatchRestoreInstanceState(state);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "failed to restoreInstanceState for widget id: " + mAppWidgetId + ", "
|
||||
+ (mInfo == null ? "null" : mInfo.provider), e);
|
||||
@@ -476,7 +499,7 @@ public class AppWidgetHostView extends FrameLayout {
|
||||
* AppWidget provider. Will animate into these new views as needed
|
||||
*/
|
||||
public void updateAppWidget(RemoteViews remoteViews) {
|
||||
this.mLastInflatedRemoteViews = remoteViews;
|
||||
mLastInflatedRemoteViews = remoteViews;
|
||||
applyRemoteViews(remoteViews, true);
|
||||
}
|
||||
|
||||
@@ -484,17 +507,23 @@ public class AppWidgetHostView extends FrameLayout {
|
||||
* Reapply the last inflated remote views, or the default view is none was inflated.
|
||||
*/
|
||||
private void reapplyLastRemoteViews() {
|
||||
SparseArray<Parcelable> savedState = new SparseArray<>();
|
||||
saveHierarchyState(savedState);
|
||||
applyRemoteViews(mLastInflatedRemoteViews, true);
|
||||
restoreHierarchyState(savedState);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
protected void applyRemoteViews(RemoteViews remoteViews, boolean useAsyncIfPossible) {
|
||||
protected void applyRemoteViews(@Nullable RemoteViews remoteViews, boolean useAsyncIfPossible) {
|
||||
boolean recycled = false;
|
||||
View content = null;
|
||||
Exception exception = null;
|
||||
|
||||
// Block state restore until the end of the apply.
|
||||
mLastInflatedRemoteViewsId = -1;
|
||||
|
||||
if (mLastExecutionSignal != null) {
|
||||
mLastExecutionSignal.cancel();
|
||||
mLastExecutionSignal = null;
|
||||
@@ -525,6 +554,7 @@ public class AppWidgetHostView extends FrameLayout {
|
||||
rvToApply.reapply(mContext, mView, mInteractionHandler, mCurrentSize,
|
||||
mColorResources);
|
||||
content = mView;
|
||||
mLastInflatedRemoteViewsId = rvToApply.computeUniqueId(remoteViews);
|
||||
recycled = true;
|
||||
if (LOGD) Log.d(TAG, "was able to recycle existing layout");
|
||||
} catch (RuntimeException e) {
|
||||
@@ -537,6 +567,7 @@ public class AppWidgetHostView extends FrameLayout {
|
||||
try {
|
||||
content = rvToApply.apply(mContext, this, mInteractionHandler,
|
||||
mCurrentSize, mColorResources);
|
||||
mLastInflatedRemoteViewsId = rvToApply.computeUniqueId(remoteViews);
|
||||
if (LOGD) Log.d(TAG, "had to inflate new layout");
|
||||
} catch (RuntimeException e) {
|
||||
exception = e;
|
||||
@@ -574,12 +605,16 @@ public class AppWidgetHostView extends FrameLayout {
|
||||
}
|
||||
}
|
||||
|
||||
private void inflateAsync(RemoteViews remoteViews) {
|
||||
private void inflateAsync(@NonNull RemoteViews remoteViews) {
|
||||
// Prepare a local reference to the remote Context so we're ready to
|
||||
// inflate any requested LayoutParams.
|
||||
mRemoteContext = getRemoteContext();
|
||||
int layoutId = remoteViews.getLayoutId();
|
||||
|
||||
if (mLastExecutionSignal != null) {
|
||||
mLastExecutionSignal.cancel();
|
||||
}
|
||||
|
||||
// If our stale view has been prepared to match active, and the new
|
||||
// layout matches, try recycling it
|
||||
if (layoutId == mLayoutId && mView != null) {
|
||||
@@ -611,7 +646,10 @@ public class AppWidgetHostView extends FrameLayout {
|
||||
private final boolean mIsReapply;
|
||||
private final int mLayoutId;
|
||||
|
||||
public ViewApplyListener(RemoteViews views, int layoutId, boolean isReapply) {
|
||||
ViewApplyListener(
|
||||
RemoteViews views,
|
||||
int layoutId,
|
||||
boolean isReapply) {
|
||||
mViews = views;
|
||||
mLayoutId = layoutId;
|
||||
mIsReapply = isReapply;
|
||||
@@ -623,6 +661,10 @@ public class AppWidgetHostView extends FrameLayout {
|
||||
mViewMode = VIEW_MODE_CONTENT;
|
||||
|
||||
applyContent(v, mIsReapply, null);
|
||||
|
||||
mLastInflatedRemoteViewsId = mViews.computeUniqueId(mLastInflatedRemoteViews);
|
||||
restoreInstanceState();
|
||||
mLastExecutionSignal = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -638,6 +680,7 @@ public class AppWidgetHostView extends FrameLayout {
|
||||
} else {
|
||||
applyContent(null, false, e);
|
||||
}
|
||||
mLastExecutionSignal = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -385,6 +385,11 @@ public class RemoteViews implements Parcelable, Filter {
|
||||
*/
|
||||
private int mViewId = View.NO_ID;
|
||||
|
||||
/**
|
||||
* Id used to uniquely identify a {@link RemoteViews} instance coming from a given provider.
|
||||
*/
|
||||
private long mProviderInstanceId = -1;
|
||||
|
||||
/** Class cookies of the Parcel this instance was read from. */
|
||||
private Map<Class, Object> mClassCookies;
|
||||
|
||||
@@ -3646,6 +3651,7 @@ public class RemoteViews implements Parcelable, Filter {
|
||||
mApplyFlags = src.mApplyFlags;
|
||||
mClassCookies = src.mClassCookies;
|
||||
mIdealSize = src.mIdealSize;
|
||||
mProviderInstanceId = src.mProviderInstanceId;
|
||||
|
||||
if (src.hasLandscapeAndPortraitLayouts()) {
|
||||
mLandscape = new RemoteViews(src.mLandscape);
|
||||
@@ -3744,6 +3750,7 @@ public class RemoteViews implements Parcelable, Filter {
|
||||
mLightBackgroundLayoutId = mPortrait.mLightBackgroundLayoutId;
|
||||
}
|
||||
mApplyFlags = parcel.readInt();
|
||||
mProviderInstanceId = parcel.readLong();
|
||||
}
|
||||
|
||||
private void readActionsFromParcel(Parcel parcel, int depth) {
|
||||
@@ -6021,6 +6028,7 @@ public class RemoteViews implements Parcelable, Filter {
|
||||
mPortrait.writeToParcel(dest, flags | PARCELABLE_ELIDE_DUPLICATES);
|
||||
}
|
||||
dest.writeInt(mApplyFlags);
|
||||
dest.writeLong(mProviderInstanceId);
|
||||
}
|
||||
|
||||
private void writeActionsToParcel(Parcel parcel) {
|
||||
@@ -6711,4 +6719,85 @@ public class RemoteViews implements Parcelable, Filter {
|
||||
public @IdRes int getViewId() {
|
||||
return mViewId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the provider instance ID.
|
||||
*
|
||||
* This should only be used by {@link com.android.server.appwidget.AppWidgetService}.
|
||||
* @hide
|
||||
*/
|
||||
public void setProviderInstanceId(long id) {
|
||||
mProviderInstanceId = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the provider instance id.
|
||||
*
|
||||
* This should uniquely identifies {@link RemoteViews} coming from a given App Widget
|
||||
* Provider. This changes each time the App Widget provider update the {@link RemoteViews} of
|
||||
* its widget. Returns -1 if the {@link RemoteViews} doesn't come from an App Widget provider.
|
||||
* @hide
|
||||
*/
|
||||
public long getProviderInstanceId() {
|
||||
return mProviderInstanceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Identify the child of this {@link RemoteViews}, or 0 if this is not a child.
|
||||
*
|
||||
* The returned value is always a small integer, currently between 0 and 17.
|
||||
*/
|
||||
private int getChildId(@NonNull RemoteViews child) {
|
||||
if (child == this) {
|
||||
return 0;
|
||||
}
|
||||
if (hasSizedRemoteViews()) {
|
||||
for (int i = 0; i < mSizedRemoteViews.size(); i++) {
|
||||
if (mSizedRemoteViews.get(i) == child) {
|
||||
return i + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hasLandscapeAndPortraitLayouts()) {
|
||||
if (mLandscape == child) {
|
||||
return 1;
|
||||
} else if (mPortrait == child) {
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
// This is not a child of this RemoteViews.
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Identify uniquely this RemoteViews, or returns -1 if not possible.
|
||||
*
|
||||
* @param parent If the {@link RemoteViews} is not a root {@link RemoteViews}, this should be
|
||||
* the parent that contains it.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public long computeUniqueId(@Nullable RemoteViews parent) {
|
||||
if (mIsRoot) {
|
||||
long viewId = getProviderInstanceId();
|
||||
if (viewId != -1) {
|
||||
viewId <<= 8;
|
||||
}
|
||||
return viewId;
|
||||
}
|
||||
if (parent == null) {
|
||||
return -1;
|
||||
}
|
||||
long viewId = parent.getProviderInstanceId();
|
||||
if (viewId == -1) {
|
||||
return -1;
|
||||
}
|
||||
int childId = parent.getChildId(this);
|
||||
if (childId == -1) {
|
||||
return -1;
|
||||
}
|
||||
viewId <<= 8;
|
||||
viewId |= childId;
|
||||
return viewId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,8 +86,8 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
|
||||
// Default height for the default loading view, in case we cannot get inflate the first view
|
||||
private static final int DEFAULT_LOADING_VIEW_HEIGHT = 50;
|
||||
|
||||
// We cache the FixedSizeRemoteViewsCaches across orientation. These are the related data
|
||||
// structures;
|
||||
// We cache the FixedSizeRemoteViewsCaches across orientation and re-inflation due to color
|
||||
// palette changes. These are the related data structures:
|
||||
private static final HashMap<RemoteViewsCacheKey, FixedSizeRemoteViewsCache>
|
||||
sCachedRemoteViewsCaches = new HashMap<>();
|
||||
private static final HashMap<RemoteViewsCacheKey, Runnable>
|
||||
|
||||
@@ -1839,6 +1839,8 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
|
||||
// For a full update we replace the RemoteViews completely.
|
||||
widget.views = views;
|
||||
}
|
||||
widget.views.setProviderInstanceId(UPDATE_COUNTER.get());
|
||||
|
||||
int memoryUsage;
|
||||
if ((UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID) &&
|
||||
(widget.views != null) &&
|
||||
@@ -1939,6 +1941,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
|
||||
|| widget.host.callbacks == null || widget.host.zombie) {
|
||||
return;
|
||||
}
|
||||
if (updateViews != null) {
|
||||
updateViews.setProviderInstanceId(requestId);
|
||||
}
|
||||
|
||||
SomeArgs args = SomeArgs.obtain();
|
||||
args.arg1 = widget.host;
|
||||
|
||||
Reference in New Issue
Block a user