diff options
Diffstat (limited to 'core/java')
| -rw-r--r-- | core/java/android/widget/RemoteViews.java | 516 | ||||
| -rw-r--r-- | core/java/android/widget/RemoteViewsAdapter.java | 6 |
2 files changed, 418 insertions, 104 deletions
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 214775a..57a3012 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -23,6 +23,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentSender; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.PorterDuff; import android.graphics.Rect; @@ -87,7 +88,32 @@ public class RemoteViews implements Parcelable, Filter { */ private MemoryUsageCounter mMemoryUsageCounter; - + /** + * Maps bitmaps to unique indicies to avoid Bitmap duplication. + */ + private BitmapCache mBitmapCache; + + /** + * Indicates whether or not this RemoteViews object is contained as a child of any other + * RemoteViews. + */ + private boolean mIsRoot = true; + + /** + * Constants to whether or not this RemoteViews is composed of a landscape and portrait + * RemoteViews. + */ + private static final int MODE_NORMAL = 0; + private static final int MODE_HAS_LANDSCAPE_AND_PORTRAIT = 1; + + /** + * Used in conjunction with the special constructor + * {@link #RemoteViews(RemoteViews, RemoteViews)} to keep track of the landscape and portrait + * RemoteViews. + */ + private RemoteViews mLandscape = null; + private RemoteViews mPortrait = null; + /** * This flag indicates whether this RemoteViews object is being created from a * RemoteViewsService for use as a child of a widget collection. This flag is used @@ -163,6 +189,10 @@ public class RemoteViews implements Parcelable, Filter { } return true; } + + public void setBitmapCache(BitmapCache bitmapCache) { + // Do nothing + } } private class SetEmptyView extends Action { @@ -643,6 +673,112 @@ public class RemoteViews implements Parcelable, Filter { } } + private static class BitmapCache { + ArrayList<Bitmap> mBitmaps; + + public BitmapCache() { + mBitmaps = new ArrayList<Bitmap>(); + } + + public BitmapCache(Parcel source) { + int count = source.readInt(); + mBitmaps = new ArrayList<Bitmap>(); + for (int i = 0; i < count; i++) { + Bitmap b = Bitmap.CREATOR.createFromParcel(source); + mBitmaps.add(b); + } + } + + public int getBitmapId(Bitmap b) { + if (b == null) { + return -1; + } else { + if (mBitmaps.contains(b)) { + return mBitmaps.indexOf(b); + } else { + mBitmaps.add(b); + return (mBitmaps.size() - 1); + } + } + } + + public Bitmap getBitmapForId(int id) { + if (id == -1 || id >= mBitmaps.size()) { + return null; + } else { + return mBitmaps.get(id); + } + } + + public void writeBitmapsToParcel(Parcel dest, int flags) { + int count = mBitmaps.size(); + dest.writeInt(count); + for (int i = 0; i < count; i++) { + mBitmaps.get(i).writeToParcel(dest, flags); + } + } + + public void assimilate(BitmapCache bitmapCache) { + ArrayList<Bitmap> bitmapsToBeAdded = bitmapCache.mBitmaps; + int count = bitmapsToBeAdded.size(); + for (int i = 0; i < count; i++) { + Bitmap b = bitmapsToBeAdded.get(i); + if (!mBitmaps.contains(b)) { + mBitmaps.add(b); + } + } + } + + public void addBitmapMemory(MemoryUsageCounter memoryCounter) { + for (int i = 0; i < mBitmaps.size(); i++) { + memoryCounter.addBitmapMemory(mBitmaps.get(i)); + } + } + } + + private class BitmapReflectionAction extends Action { + int bitmapId; + int viewId; + Bitmap bitmap; + String methodName; + + BitmapReflectionAction(int viewId, String methodName, Bitmap bitmap) { + this.bitmap = bitmap; + this.viewId = viewId; + this.methodName = methodName; + bitmapId = mBitmapCache.getBitmapId(bitmap); + } + + BitmapReflectionAction(Parcel in) { + viewId = in.readInt(); + methodName = in.readString(); + bitmapId = in.readInt(); + bitmap = mBitmapCache.getBitmapForId(bitmapId); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(TAG); + dest.writeInt(viewId); + dest.writeString(methodName); + dest.writeInt(bitmapId); + } + + @Override + public void apply(View root, ViewGroup rootParent) throws ActionException { + ReflectionAction ra = new ReflectionAction(viewId, methodName, ReflectionAction.BITMAP, + bitmap); + ra.apply(root, rootParent); + } + + @Override + public void setBitmapCache(BitmapCache bitmapCache) { + bitmapId = bitmapCache.getBitmapId(bitmap); + } + + public final static int TAG = 12; + } + /** * Base class for the reflection actions. */ @@ -894,24 +1030,7 @@ public class RemoteViews implements Parcelable, Filter { case BITMAP: if (this.value != null) { final Bitmap b = (Bitmap) this.value; - final Bitmap.Config c = b.getConfig(); - // If we don't know, be pessimistic and assume 4 - int bpp = 4; - if (c != null) { - switch (c) { - case ALPHA_8: - bpp = 1; - break; - case RGB_565: - case ARGB_4444: - bpp = 2; - break; - case ARGB_8888: - bpp = 4; - break; - } - } - counter.bitmapIncrement(b.getWidth() * b.getHeight() * bpp); + counter.addBitmapMemory(b); } break; default: @@ -920,6 +1039,16 @@ public class RemoteViews implements Parcelable, Filter { } } + private void configureRemoteViewsAsChild(RemoteViews rv) { + mBitmapCache.assimilate(rv.mBitmapCache); + rv.setBitmapCache(mBitmapCache); + rv.setNotRoot(); + } + + void setNotRoot() { + mIsRoot = false; + } + /** * Equivalent to calling {@link ViewGroup#addView(View)} after inflating the * given {@link RemoteViews}, or calling {@link ViewGroup#removeAllViews()} @@ -929,17 +1058,29 @@ public class RemoteViews implements Parcelable, Filter { public ViewGroupAction(int viewId, RemoteViews nestedViews) { this.viewId = viewId; this.nestedViews = nestedViews; + configureRemoteViewsAsChild(nestedViews); } - public ViewGroupAction(Parcel parcel) { + public ViewGroupAction(Parcel parcel, BitmapCache bitmapCache) { viewId = parcel.readInt(); - nestedViews = parcel.readParcelable(null); + boolean nestedViewsNull = parcel.readInt() == 0; + if (!nestedViewsNull) { + nestedViews = new RemoteViews(parcel, bitmapCache); + } else { + nestedViews = null; + } } public void writeToParcel(Parcel dest, int flags) { dest.writeInt(TAG); dest.writeInt(viewId); - dest.writeParcelable(nestedViews, 0 /* no flags */); + if (nestedViews != null) { + dest.writeInt(1); + nestedViews.writeToParcel(dest, flags); + } else { + // signifies null + dest.writeInt(0); + } } @Override @@ -959,7 +1100,14 @@ public class RemoteViews implements Parcelable, Filter { @Override public void updateMemoryUsageEstimate(MemoryUsageCounter counter) { if (nestedViews != null) { - counter.bitmapIncrement(nestedViews.estimateBitmapMemoryUsage()); + counter.increment(nestedViews.estimateMemoryUsage()); + } + } + + @Override + public void setBitmapCache(BitmapCache bitmapCache) { + if (nestedViews != null) { + nestedViews.setBitmapCache(bitmapCache); } } @@ -1027,18 +1175,39 @@ public class RemoteViews implements Parcelable, Filter { */ private class MemoryUsageCounter { public void clear() { - mBitmapHeapMemoryUsage = 0; + mMemoryUsage = 0; } - public void bitmapIncrement(int numBytes) { - mBitmapHeapMemoryUsage += numBytes; + public void increment(int numBytes) { + mMemoryUsage += numBytes; } - public int getBitmapHeapMemoryUsage() { - return mBitmapHeapMemoryUsage; + public int getMemoryUsage() { + return mMemoryUsage; } - int mBitmapHeapMemoryUsage; + public void addBitmapMemory(Bitmap b) { + final Bitmap.Config c = b.getConfig(); + // If we don't know, be pessimistic and assume 4 + int bpp = 4; + if (c != null) { + switch (c) { + case ALPHA_8: + bpp = 1; + break; + case RGB_565: + case ARGB_4444: + bpp = 2; + break; + case ARGB_8888: + bpp = 4; + break; + } + } + increment(b.getWidth() * b.getHeight() * bpp); + } + + int mMemoryUsage; } /** @@ -1051,62 +1220,122 @@ public class RemoteViews implements Parcelable, Filter { public RemoteViews(String packageName, int layoutId) { mPackage = packageName; mLayoutId = layoutId; + mBitmapCache = new BitmapCache(); // setup the memory usage statistics mMemoryUsageCounter = new MemoryUsageCounter(); recalculateMemoryUsage(); } + private boolean hasLandscapeAndPortraitLayouts() { + return (mLandscape != null) && (mPortrait != null); + } + + /** + * Create a new RemoteViews object that will inflate as the specified + * landspace or portrait RemoteViews, depending on the current configuration. + * + * @param landscape The RemoteViews to inflate in landscape configuration + * @param portrait The RemoteViews to inflate in portrait configuration + */ + public RemoteViews(RemoteViews landscape, RemoteViews portrait) { + if (landscape == null || portrait == null) { + throw new RuntimeException("Both RemoteViews must be non-null"); + } + if (landscape.getPackage().compareTo(portrait.getPackage()) != 0) { + throw new RuntimeException("Both RemoteViews must share the same package"); + } + mPackage = portrait.getPackage(); + mLayoutId = portrait.getLayoutId(); + + mLandscape = landscape; + mPortrait = portrait; + + // setup the memory usage statistics + mMemoryUsageCounter = new MemoryUsageCounter(); + + mBitmapCache = new BitmapCache(); + configureRemoteViewsAsChild(landscape); + configureRemoteViewsAsChild(portrait); + + recalculateMemoryUsage(); + } + /** * Reads a RemoteViews object from a parcel. * * @param parcel */ public RemoteViews(Parcel parcel) { - mPackage = parcel.readString(); - mLayoutId = parcel.readInt(); - mIsWidgetCollectionChild = parcel.readInt() == 1 ? true : false; + this(parcel, null); + } - int count = parcel.readInt(); - if (count > 0) { - mActions = new ArrayList<Action>(count); - for (int i=0; i<count; i++) { - int tag = parcel.readInt(); - switch (tag) { - case SetOnClickPendingIntent.TAG: - mActions.add(new SetOnClickPendingIntent(parcel)); - break; - case SetDrawableParameters.TAG: - mActions.add(new SetDrawableParameters(parcel)); - break; - case ReflectionAction.TAG: - mActions.add(new ReflectionAction(parcel)); - break; - case ViewGroupAction.TAG: - mActions.add(new ViewGroupAction(parcel)); - break; - case ReflectionActionWithoutParams.TAG: - mActions.add(new ReflectionActionWithoutParams(parcel)); - break; - case SetEmptyView.TAG: - mActions.add(new SetEmptyView(parcel)); - break; - case SetPendingIntentTemplate.TAG: - mActions.add(new SetPendingIntentTemplate(parcel)); - break; - case SetOnClickFillInIntent.TAG: - mActions.add(new SetOnClickFillInIntent(parcel)); - break; - case SetRemoteViewsAdapterIntent.TAG: - mActions.add(new SetRemoteViewsAdapterIntent(parcel)); - break; - case TextViewDrawableAction.TAG: - mActions.add(new TextViewDrawableAction(parcel)); - break; - default: - throw new ActionException("Tag " + tag + " not found"); + private RemoteViews(Parcel parcel, BitmapCache bitmapCache) { + int mode = parcel.readInt(); + + // We only store a bitmap cache in the root of the RemoteViews. + if (bitmapCache == null) { + mBitmapCache = new BitmapCache(parcel); + } else { + setBitmapCache(bitmapCache); + setNotRoot(); + } + + if (mode == MODE_NORMAL) { + mPackage = parcel.readString(); + mLayoutId = parcel.readInt(); + mIsWidgetCollectionChild = parcel.readInt() == 1 ? true : false; + + int count = parcel.readInt(); + if (count > 0) { + mActions = new ArrayList<Action>(count); + for (int i=0; i<count; i++) { + int tag = parcel.readInt(); + switch (tag) { + case SetOnClickPendingIntent.TAG: + mActions.add(new SetOnClickPendingIntent(parcel)); + break; + case SetDrawableParameters.TAG: + mActions.add(new SetDrawableParameters(parcel)); + break; + case ReflectionAction.TAG: + mActions.add(new ReflectionAction(parcel)); + break; + case ViewGroupAction.TAG: + mActions.add(new ViewGroupAction(parcel, mBitmapCache)); + break; + case ReflectionActionWithoutParams.TAG: + mActions.add(new ReflectionActionWithoutParams(parcel)); + break; + case SetEmptyView.TAG: + mActions.add(new SetEmptyView(parcel)); + break; + case SetPendingIntentTemplate.TAG: + mActions.add(new SetPendingIntentTemplate(parcel)); + break; + case SetOnClickFillInIntent.TAG: + mActions.add(new SetOnClickFillInIntent(parcel)); + break; + case SetRemoteViewsAdapterIntent.TAG: + mActions.add(new SetRemoteViewsAdapterIntent(parcel)); + break; + case TextViewDrawableAction.TAG: + mActions.add(new TextViewDrawableAction(parcel)); + break; + case BitmapReflectionAction.TAG: + mActions.add(new BitmapReflectionAction(parcel)); + break; + default: + throw new ActionException("Tag " + tag + " not found"); + } } } + } else { + // MODE_HAS_LANDSCAPE_AND_PORTRAIT + mLandscape = new RemoteViews(parcel, mBitmapCache); + mPortrait = new RemoteViews(parcel, mBitmapCache); + mPackage = mPortrait.getPackage(); + mLayoutId = mPortrait.getLayoutId(); } // setup the memory usage statistics @@ -1116,11 +1345,18 @@ public class RemoteViews implements Parcelable, Filter { @Override public RemoteViews clone() { - final RemoteViews that = new RemoteViews(mPackage, mLayoutId); - if (mActions != null) { - that.mActions = (ArrayList<Action>)mActions.clone(); - } + RemoteViews that; + if (!hasLandscapeAndPortraitLayouts()) { + that = new RemoteViews(mPackage, mLayoutId); + if (mActions != null) { + that.mActions = (ArrayList<Action>)mActions.clone(); + } + } else { + RemoteViews land = mLandscape.clone(); + RemoteViews port = mPortrait.clone(); + that = new RemoteViews(land, port); + } // update the memory usage stats of the cloned RemoteViews that.recalculateMemoryUsage(); return that; @@ -1130,6 +1366,13 @@ public class RemoteViews implements Parcelable, Filter { return mPackage; } + /** + * Reutrns 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. + * + * @return the layout id. + */ public int getLayoutId() { return mLayoutId; } @@ -1151,20 +1394,47 @@ public class RemoteViews implements Parcelable, Filter { private void recalculateMemoryUsage() { mMemoryUsageCounter.clear(); - // Accumulate the memory usage for each action - if (mActions != null) { - final int count = mActions.size(); - for (int i= 0; i < count; ++i) { - mActions.get(i).updateMemoryUsageEstimate(mMemoryUsageCounter); + if (!hasLandscapeAndPortraitLayouts()) { + // Accumulate the memory usage for each action + if (mActions != null) { + final int count = mActions.size(); + for (int i= 0; i < count; ++i) { + mActions.get(i).updateMemoryUsageEstimate(mMemoryUsageCounter); + } + } + if (mIsRoot) { + mBitmapCache.addBitmapMemory(mMemoryUsageCounter); } + } else { + mMemoryUsageCounter.increment(mLandscape.estimateMemoryUsage()); + mMemoryUsageCounter.increment(mPortrait.estimateMemoryUsage()); + mBitmapCache.addBitmapMemory(mMemoryUsageCounter); + } + } + + /** + * Recursively sets BitmapCache in the hierarchy and update the bitmap ids. + */ + private void setBitmapCache(BitmapCache bitmapCache) { + mBitmapCache = bitmapCache; + if (!hasLandscapeAndPortraitLayouts()) { + if (mActions != null) { + final int count = mActions.size(); + for (int i= 0; i < count; ++i) { + mActions.get(i).setBitmapCache(bitmapCache); + } + } + } else { + mLandscape.setBitmapCache(bitmapCache); + mPortrait.setBitmapCache(bitmapCache); } } /** * Returns an estimate of the bitmap heap memory usage for this RemoteViews. */ - int estimateBitmapMemoryUsage() { - return mMemoryUsageCounter.getBitmapHeapMemoryUsage(); + int estimateMemoryUsage() { + return mMemoryUsageCounter.getMemoryUsage(); } /** @@ -1173,6 +1443,11 @@ public class RemoteViews implements Parcelable, Filter { * @param a The action to add */ private void addAction(Action a) { + if (hasLandscapeAndPortraitLayouts()) { + throw new RuntimeException("RemoteViews specifying separate landscape and portrait" + + " layouts cannot be modified. Instead, fully configure the landscape and" + + " portrait layouts individually before constructing the combined layout."); + } if (mActions == null) { mActions = new ArrayList<Action>(); } @@ -1644,7 +1919,7 @@ public class RemoteViews implements Parcelable, Filter { * @param value The value to pass to the method. */ public void setBitmap(int viewId, String methodName, Bitmap value) { - addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BITMAP, value)); + addAction(new BitmapReflectionAction(viewId, methodName, value)); } /** @@ -1679,6 +1954,18 @@ public class RemoteViews implements Parcelable, Filter { setCharSequence(viewId, "setContentDescription", contentDescription); } + private RemoteViews getRemoteViewsToApply(Context context) { + if (hasLandscapeAndPortraitLayouts()) { + int orientation = context.getResources().getConfiguration().orientation; + if (orientation == Configuration.ORIENTATION_LANDSCAPE) { + return mLandscape; + } else { + return mPortrait; + } + } + return this; + } + /** * Inflates the view hierarchy represented by this object and applies * all of the actions. @@ -1691,6 +1978,8 @@ public class RemoteViews implements Parcelable, Filter { * @return The inflated view hierarchy */ public View apply(Context context, ViewGroup parent) { + RemoteViews rvToApply = getRemoteViewsToApply(context); + View result; Context c = prepareContext(context); @@ -1701,13 +1990,13 @@ public class RemoteViews implements Parcelable, Filter { inflater = inflater.cloneInContext(c); inflater.setFilter(this); - result = inflater.inflate(mLayoutId, parent, false); + result = inflater.inflate(rvToApply.getLayoutId(), parent, false); - performApply(result, parent); + rvToApply.performApply(result, parent); return result; } - + /** * Applies all of the actions to the provided view. * @@ -1717,8 +2006,20 @@ public class RemoteViews implements Parcelable, Filter { * the {@link #apply(Context,ViewGroup)} call. */ public void reapply(Context context, View v) { + RemoteViews rvToApply = getRemoteViewsToApply(context); + + // In the case that a view has this RemoteViews applied in one orientation, is persisted + // across orientation change, and has the RemoteViews re-applied in the new orientation, + // we throw an exception, since the layouts may be completely unrelated. + if (hasLandscapeAndPortraitLayouts()) { + if (v.getId() != rvToApply.getLayoutId()) { + throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" + + " that does not share the same root layout id."); + } + } + prepareContext(context); - performApply(v, (ViewGroup) v.getParent()); + rvToApply.performApply(v, (ViewGroup) v.getParent()); } private void performApply(View v, ViewGroup parent) { @@ -1757,25 +2058,42 @@ public class RemoteViews implements Parcelable, Filter { public boolean onLoadClass(Class clazz) { return clazz.isAnnotationPresent(RemoteView.class); } - + public int describeContents() { return 0; } public void writeToParcel(Parcel dest, int flags) { - dest.writeString(mPackage); - dest.writeInt(mLayoutId); - dest.writeInt(mIsWidgetCollectionChild ? 1 : 0); - int count; - if (mActions != null) { - count = mActions.size(); + if (!hasLandscapeAndPortraitLayouts()) { + dest.writeInt(MODE_NORMAL); + // We only write the bitmap cache if we are the root RemoteViews, as this cache + // is shared by all children. + if (mIsRoot) { + mBitmapCache.writeBitmapsToParcel(dest, flags); + } + dest.writeString(mPackage); + dest.writeInt(mLayoutId); + dest.writeInt(mIsWidgetCollectionChild ? 1 : 0); + int count; + if (mActions != null) { + count = mActions.size(); + } else { + count = 0; + } + dest.writeInt(count); + for (int i=0; i<count; i++) { + Action a = mActions.get(i); + a.writeToParcel(dest, 0); + } } else { - count = 0; - } - dest.writeInt(count); - for (int i=0; i<count; i++) { - Action a = mActions.get(i); - a.writeToParcel(dest, 0); + dest.writeInt(MODE_HAS_LANDSCAPE_AND_PORTRAIT); + // We only write the bitmap cache if we are the root RemoteViews, as this cache + // is shared by all children. + if (mIsRoot) { + mBitmapCache.writeBitmapsToParcel(dest, flags); + } + mLandscape.writeToParcel(dest, flags); + mPortrait.writeToParcel(dest, flags); } } diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java index 8067435..f266d50 100644 --- a/core/java/android/widget/RemoteViewsAdapter.java +++ b/core/java/android/widget/RemoteViewsAdapter.java @@ -595,7 +595,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback for (Integer i : mIndexRemoteViews.keySet()) { final RemoteViews v = mIndexRemoteViews.get(i); if (v != null) { - mem += v.estimateBitmapMemoryUsage(); + mem += v.estimateMemoryUsage(); } } return mem; @@ -942,10 +942,6 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback * which wouldn't otherwise be possible. */ public void setVisibleRangeHint(int lowerBound, int upperBound) { - if (lowerBound < 0 || upperBound < 0) { - throw new RuntimeException("Attempted to set invalid range: lowerBound="+lowerBound + - "," + "upperBound="+upperBound); - } mVisibleWindowLowerBound = lowerBound; mVisibleWindowUpperBound = upperBound; } |
