Jailing the saved instance state of all the dynamically generated views

Using itemId instead of generating a new id for each item. This is because
if the process gets killed, View.generateId will get reset but we will still
receive the generated item id map in onRestoreInstance. This will cause
conflicts with newly generated item ids.

We wrap all the generated homescreen views inside a single sparse array. This
ensures that we do not cause any conflict with dynamically generated views in
other parts of the UI.

Change-Id: I6fe69c2e1dd463402f51222715fae31b9d4dd240
This commit is contained in:
Sunny Goyal 2015-08-27 17:45:46 -07:00
parent fafd4c276e
commit e2fd14b9f6
5 changed files with 89 additions and 43 deletions

View File

@ -76,6 +76,9 @@
<!-- View ID to use for QSB widget -->
<item type="id" name="qsb_widget" />
<!-- View ID used by cell layout to jail its content -->
<item type="id" name="cell_layout_jail_id" />
<!-- Accessibility actions -->
<item type="id" name="action_remove" />
<item type="id" name="action_uninstall" />

View File

@ -24,7 +24,6 @@ import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
@ -54,6 +53,7 @@ import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
import com.android.launcher3.accessibility.FolderAccessibilityHelper;
import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
import com.android.launcher3.config.ProviderConfig;
import com.android.launcher3.util.ParcelableSparseArray;
import com.android.launcher3.util.Thunk;
import java.util.ArrayList;
@ -85,6 +85,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
private int mMaxGap;
private boolean mDropPending = false;
private boolean mIsDragTarget = true;
private boolean mJailContent = true;
// These are temporary variables to prevent having to allocate a new object just to
// return an (x, y) value from helper functions. Do NOT use them to maintain other state.
@ -188,7 +189,6 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
mLauncher = (Launcher) context;
DeviceProfile grid = mLauncher.getDeviceProfile();
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
mCellWidth = mCellHeight = -1;
mFixedCellWidth = mFixedCellHeight = -1;
@ -202,10 +202,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
mPreviousReorderDirection[0] = INVALID_DIRECTION;
mPreviousReorderDirection[1] = INVALID_DIRECTION;
a.recycle();
setAlwaysDrawnWithCacheEnabled(false);
final Resources res = getResources();
mHotseatScale = (float) grid.hotseatIconSizePx / grid.iconSizePx;
@ -425,6 +422,32 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
}
}
public void disableJailContent() {
mJailContent = false;
}
@Override
protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
if (mJailContent) {
ParcelableSparseArray jail = getJailedArray(container);
super.dispatchSaveInstanceState(jail);
container.put(R.id.cell_layout_jail_id, jail);
} else {
super.dispatchSaveInstanceState(container);
}
}
@Override
protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
super.dispatchRestoreInstanceState(mJailContent ? getJailedArray(container) : container);
}
private ParcelableSparseArray getJailedArray(SparseArray<Parcelable> container) {
final Parcelable parcelable = container.get(R.id.cell_layout_jail_id);
return parcelable instanceof ParcelableSparseArray ?
(ParcelableSparseArray) parcelable : new ParcelableSparseArray();
}
public boolean getIsDragOverlapping() {
return mIsDragOverlapping;
}

View File

@ -126,7 +126,6 @@ import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Default launcher application.
@ -183,8 +182,6 @@ public class Launcher extends Activity
private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_INFO = "launcher.add_widget_info";
// Type: parcelable
private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_ID = "launcher.add_widget_id";
// Type: int[]
private static final String RUNTIME_STATE_VIEW_IDS = "launcher.view_ids";
static final String INTRO_SCREEN_DISMISSED = "launcher.intro_screen_dismissed";
static final String FIRST_RUN_ACTIVITY_DISPLAYED = "launcher.first_run_activity_displayed";
@ -218,9 +215,6 @@ public class Launcher extends Activity
private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500;
private static final int ACTIVITY_START_DELAY = 1000;
private HashMap<Integer, Integer> mItemIdToViewId = new HashMap<Integer, Integer>();
private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);
// How long to wait before the new-shortcut animation automatically pans the workspace
private static int NEW_APPS_PAGE_MOVE_DELAY = 500;
private static int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5;
@ -635,34 +629,12 @@ public class Launcher extends Activity
return !isWorkspaceLoading();
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public static int generateViewId() {
if (Utilities.ATLEAST_JB_MR1) {
return View.generateViewId();
} else {
// View.generateViewId() is not available. The following fallback logic is a copy
// of its implementation.
for (;;) {
final int result = sNextGeneratedId.get();
// aapt-generated IDs have the high byte nonzero; clamp to the range under that.
int newValue = result + 1;
if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.
if (sNextGeneratedId.compareAndSet(result, newValue)) {
return result;
}
}
}
}
public int getViewIdForItem(ItemInfo info) {
// This cast is safe given the > 2B range for int.
int itemId = (int) info.id;
if (mItemIdToViewId.containsKey(itemId)) {
return mItemIdToViewId.get(itemId);
}
int viewId = generateViewId();
mItemIdToViewId.put(itemId, viewId);
return viewId;
// aapt-generated IDs have the high byte nonzero; clamp to the range under that.
// This cast is safe as long as the id < 0x00FFFFFF
// Since we jail all the dynamically generated views, there should be no clashes
// with any other views.
return (int) info.id;
}
/**
@ -1281,7 +1253,6 @@ public class Launcher extends Activity
*
* @param savedState The previous state.
*/
@SuppressWarnings("unchecked")
private void restoreState(Bundle savedState) {
if (savedState == null) {
return;
@ -1310,9 +1281,6 @@ public class Launcher extends Activity
setWaitingForResult(true);
mRestoring = true;
}
mItemIdToViewId = (HashMap<Integer, Integer>)
savedState.getSerializable(RUNTIME_STATE_VIEW_IDS);
}
/**
@ -1936,7 +1904,6 @@ public class Launcher extends Activity
// Save the current widgets tray?
// TODO(hyunyoungs)
outState.putSerializable(RUNTIME_STATE_VIEW_IDS, mItemIdToViewId);
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onSaveInstanceState(outState);

View File

@ -568,6 +568,7 @@ public class Workspace extends PagedView
CellLayout customScreen = (CellLayout)
mLauncher.getLayoutInflater().inflate(R.layout.workspace_screen, this, false);
customScreen.disableDragTarget();
customScreen.disableJailContent();
mWorkspaceScreens.put(CUSTOM_CONTENT_SCREEN_ID, customScreen);
mScreenOrder.add(0, CUSTOM_CONTENT_SCREEN_ID);

View File

@ -0,0 +1,52 @@
/**
* Copyright (C) 2015 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 com.android.launcher3.util;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.SparseArray;
public class ParcelableSparseArray extends SparseArray<Parcelable> implements Parcelable {
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel dest, int flags) {
final int count = size();
dest.writeInt(count);
for (int i = 0; i < count; i++) {
dest.writeInt(keyAt(i));
dest.writeParcelable(valueAt(i), 0);
}
}
public static final Parcelable.Creator<ParcelableSparseArray> CREATOR =
new Parcelable.Creator<ParcelableSparseArray>() {
public ParcelableSparseArray createFromParcel(Parcel source) {
final ParcelableSparseArray array = new ParcelableSparseArray();
final ClassLoader loader = array.getClass().getClassLoader();
final int count = source.readInt();
for (int i = 0; i < count; i++) {
array.put(source.readInt(), source.readParcelable(loader));
}
return array;
}
public ParcelableSparseArray[] newArray(int size) {
return new ParcelableSparseArray[size];
}
};
}