diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 063288e2adfc3..8bd63dfc20752 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -60,6 +60,7 @@ import android.widget.AdapterView.OnItemClickListener; import libcore.util.Objects; import com.android.internal.R; +import com.android.internal.util.Preconditions; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -1096,6 +1097,13 @@ public class RemoteViews implements Parcelable, Filter { memoryCounter.addBitmapMemory(mBitmaps.get(i)); } } + + @Override + protected BitmapCache clone() { + BitmapCache bitmapCache = new BitmapCache(); + bitmapCache.mBitmaps.addAll(mBitmaps); + return bitmapCache; + } } private class BitmapReflectionAction extends Action { @@ -2227,10 +2235,21 @@ public class RemoteViews implements Parcelable, Filter { public RemoteViews clone() { + Preconditions.checkState(mIsRoot, "RemoteView has been attached to another RemoteView. " + + "May only clone the root of a RemoteView hierarchy."); + Parcel p = Parcel.obtain(); + + // Do not parcel the Bitmap cache - doing so creates an expensive copy of all bitmaps. + // Instead pretend we're not owning the cache while parceling. + mIsRoot = false; writeToParcel(p, 0); p.setDataPosition(0); - RemoteViews rv = new RemoteViews(p); + mIsRoot = true; + + RemoteViews rv = new RemoteViews(p, mBitmapCache.clone()); + rv.mIsRoot = true; + p.recycle(); return rv; } @@ -2240,7 +2259,7 @@ public class RemoteViews implements Parcelable, Filter { } /** - * Reutrns the layout id of the root layout associated with this RemoteViews. In the case + * Returns the layout id of the root layout associated with this RemoteViews. In the case * that the RemoteViews has both a landscape and portrait root, this will return the layout * id associated with the portrait layout. * diff --git a/core/tests/coretests/res/layout/remote_views_test.xml b/core/tests/coretests/res/layout/remote_views_test.xml new file mode 100644 index 0000000000000..c5f7a47c1e968 --- /dev/null +++ b/core/tests/coretests/res/layout/remote_views_test.xml @@ -0,0 +1,32 @@ + + + + + + + + + + diff --git a/core/tests/coretests/src/android/view/RemoteViewsTest.java b/core/tests/coretests/src/android/view/RemoteViewsTest.java new file mode 100644 index 0000000000000..9c4fd70473342 --- /dev/null +++ b/core/tests/coretests/src/android/view/RemoteViewsTest.java @@ -0,0 +1,127 @@ +/* + * 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.view; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.RemoteViews; +import android.widget.TextView; + +import com.android.frameworks.coretests.R; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; + +/** + * Tests for RemoteViews. + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class RemoteViewsTest { + + @Rule + public final ExpectedException exception = ExpectedException.none(); + + private Context mContext; + private String mPackage; + private LinearLayout mContainer; + + @Before + public void setup() { + mContext = InstrumentationRegistry.getContext(); + mPackage = mPackage; + mContainer = new LinearLayout(mContext); + } + + @Test + public void clone_doesNotCopyBitmap() { + RemoteViews original = new RemoteViews(mPackage, R.layout.remote_views_test); + Bitmap bitmap = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888); + + original.setImageViewBitmap(R.id.image, bitmap); + RemoteViews clone = original.clone(); + View inflated = clone.apply(mContext, mContainer); + + Drawable drawable = ((ImageView) inflated.findViewById(R.id.image)).getDrawable(); + assertSame(bitmap, ((BitmapDrawable)drawable).getBitmap()); + } + + @Test + public void clone_originalCanStillBeApplied() { + RemoteViews original = new RemoteViews(mPackage, R.layout.remote_views_test); + + RemoteViews clone = original.clone(); + + clone.apply(mContext, mContainer); + } + + @Test + public void clone_clones() { + RemoteViews original = new RemoteViews(mPackage, R.layout.remote_views_test); + + RemoteViews clone = original.clone(); + original.setTextViewText(R.id.text, "test"); + View inflated = clone.apply(mContext, mContainer); + + TextView textView = (TextView) inflated.findViewById(R.id.text); + assertEquals("", textView.getText()); + } + + @Test + public void clone_child_fails() { + RemoteViews original = new RemoteViews(mPackage, R.layout.remote_views_test); + RemoteViews child = new RemoteViews(mPackage, R.layout.remote_views_test); + + original.addView(R.id.layout, child); + + exception.expect(IllegalStateException.class); + RemoteViews clone = child.clone(); + } + + @Test + public void clone_repeatedly() { + RemoteViews original = new RemoteViews(mPackage, R.layout.remote_views_test); + + original.clone(); + original.clone(); + + original.apply(mContext, mContainer); + } + + @Test + public void clone_chained() { + RemoteViews original = new RemoteViews(mPackage, R.layout.remote_views_test); + + RemoteViews clone = original.clone().clone(); + + clone.apply(mContext, mContainer); + } + +}