Let RemoteViews nest children, allow ViewFlipper.

This change allows applications to nest children RemoteViews
inside an existing set of RemoteViews.  These nested views
are inflated and treated as addView() calls.

This change also allows ViewFlipper through RemoteViews, and
adds logic surpress flipping when the parent window is
detached or behind the lockscreen.  Also fixes ViewAnimator
to observe the measureAllChildren flag when set.

Fixes http://b/2239905
diff --git a/api/current.xml b/api/current.xml
index 6ddd439..808e91c 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -1967,6 +1967,17 @@
  visibility="public"
 >
 </field>
+<field name="autoStart"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843446"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="autoText"
  type="int"
  transient="false"
@@ -189616,6 +189627,21 @@
 <parameter name="parcel" type="android.os.Parcel">
 </parameter>
 </constructor>
+<method name="addView"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="viewId" type="int">
+</parameter>
+<parameter name="nestedView" type="android.widget.RemoteViews">
+</parameter>
+</method>
 <method name="apply"
  return="android.view.View"
  abstract="false"
@@ -189692,6 +189718,19 @@
 <parameter name="v" type="android.view.View">
 </parameter>
 </method>
+<method name="removeAllViews"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="viewId" type="int">
+</parameter>
+</method>
 <method name="setBitmap"
  return="void"
  abstract="false"
@@ -196277,6 +196316,17 @@
 <parameter name="attrs" type="android.util.AttributeSet">
 </parameter>
 </constructor>
+<method name="isAutoStart"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="isFlipping"
  return="boolean"
  abstract="false"
@@ -196288,6 +196338,19 @@
  visibility="public"
 >
 </method>
+<method name="setAutoStart"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="autoStart" type="boolean">
+</parameter>
+</method>
 <method name="setFlipInterval"
  return="void"
  abstract="false"
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 6771711..3b1f7a0 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -457,6 +457,46 @@
         }
     }
 
+    /**
+     * Equivalent to calling {@link ViewGroup#addView(View)} after inflating the
+     * given {@link RemoteViews}, or calling {@link ViewGroup#removeAllViews()}
+     * when null. This allows users to build "nested" {@link RemoteViews}.
+     */
+    private class ViewGroupAction extends Action {
+        public ViewGroupAction(int viewId, RemoteViews nestedViews) {
+            this.viewId = viewId;
+            this.nestedViews = nestedViews;
+        }
+
+        public ViewGroupAction(Parcel parcel) {
+            viewId = parcel.readInt();
+            nestedViews = parcel.readParcelable(null);
+        }
+
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(TAG);
+            dest.writeInt(viewId);
+            dest.writeParcelable(nestedViews, 0 /* no flags */);
+        }
+
+        @Override
+        public void apply(View root) {
+            final Context context = root.getContext();
+            final ViewGroup target = (ViewGroup) root.findViewById(viewId);
+            if (nestedViews != null) {
+                // Inflate nested views and add as children
+                target.addView(nestedViews.apply(context, target));
+            } else if (target != null) {
+                // Clear all children when nested views omitted
+                target.removeAllViews();
+            }
+        }
+
+        int viewId;
+        RemoteViews nestedViews;
+
+        public final static int TAG = 4;
+    }
 
     /**
      * Create a new RemoteViews object that will display the views contained
@@ -493,6 +533,9 @@
                 case ReflectionAction.TAG:
                     mActions.add(new ReflectionAction(parcel));
                     break;
+                case ViewGroupAction.TAG:
+                    mActions.add(new ViewGroupAction(parcel));
+                    break;
                 default:
                     throw new ActionException("Tag " + tag + " not found");
                 }
@@ -519,7 +562,31 @@
         }
         mActions.add(a);
     }
-    
+
+    /**
+     * Equivalent to calling {@link ViewGroup#addView(View)} after inflating the
+     * given {@link RemoteViews}. This allows users to build "nested"
+     * {@link RemoteViews}. In cases where consumers of {@link RemoteViews} may
+     * recycle layouts, use {@link #removeAllViews(int)} to clear any existing
+     * children.
+     *
+     * @param viewId The id of the parent {@link ViewGroup} to add child into.
+     * @param nestedView {@link RemoteViews} that describes the child.
+     */
+    public void addView(int viewId, RemoteViews nestedView) {
+        addAction(new ViewGroupAction(viewId, nestedView));
+    }
+
+    /**
+     * Equivalent to calling {@link ViewGroup#removeAllViews()}.
+     *
+     * @param viewId The id of the parent {@link ViewGroup} to remove all
+     *            children from.
+     */
+    public void removeAllViews(int viewId) {
+        addAction(new ViewGroupAction(viewId, null));
+    }
+
     /**
      * Equivalent to calling View.setVisibility
      * 
diff --git a/core/java/android/widget/ViewAnimator.java b/core/java/android/widget/ViewAnimator.java
index fa8935e3..4112501 100644
--- a/core/java/android/widget/ViewAnimator.java
+++ b/core/java/android/widget/ViewAnimator.java
@@ -43,7 +43,7 @@
 
     public ViewAnimator(Context context) {
         super(context);
-        initViewAnimator();
+        initViewAnimator(context, null);
     }
 
     public ViewAnimator(Context context, AttributeSet attrs) {
@@ -61,11 +61,28 @@
         }
         a.recycle();
 
-        initViewAnimator();
+        initViewAnimator(context, attrs);
     }
 
-    private void initViewAnimator() {
-        mMeasureAllChildren = true;
+    /**
+     * Initialize this {@link ViewAnimator}, possibly setting
+     * {@link #setMeasureAllChildren(boolean)} based on {@link FrameLayout} flags.
+     */
+    private void initViewAnimator(Context context, AttributeSet attrs) {
+        if (attrs == null) {
+            // For compatibility, always measure children when undefined.
+            mMeasureAllChildren = true;
+            return;
+        }
+
+        // For compatibility, default to measure children, but allow XML
+        // attribute to override.
+        final TypedArray a = context.obtainStyledAttributes(attrs,
+                com.android.internal.R.styleable.FrameLayout);
+        final boolean measureAllChildren = a.getBoolean(
+                com.android.internal.R.styleable.FrameLayout_measureAllChildren, true);
+        setMeasureAllChildren(measureAllChildren);
+        a.recycle();
     }
     
     /**
diff --git a/core/java/android/widget/ViewFlipper.java b/core/java/android/widget/ViewFlipper.java
index 8a7946b..aee25b0 100644
--- a/core/java/android/widget/ViewFlipper.java
+++ b/core/java/android/widget/ViewFlipper.java
@@ -16,12 +16,15 @@
 
 package android.widget;
 
-
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.res.TypedArray;
 import android.os.Handler;
 import android.os.Message;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.widget.RemoteViews.RemoteView;
 
 /**
@@ -30,10 +33,22 @@
  * requested, can automatically flip between each child at a regular interval.
  *
  * @attr ref android.R.styleable#ViewFlipper_flipInterval
+ * @attr ref android.R.styleable#ViewFlipper_autoStart
  */
+@RemoteView
 public class ViewFlipper extends ViewAnimator {
-    private int mFlipInterval = 3000;
-    private boolean mKeepFlipping = false;
+    private static final String TAG = "ViewFlipper";
+    private static final boolean LOGD = true;
+
+    private static final int DEFAULT_INTERVAL = 3000;
+
+    private int mFlipInterval = DEFAULT_INTERVAL;
+    private boolean mAutoStart = false;
+
+    private boolean mRunning = false;
+    private boolean mStarted = false;
+    private boolean mVisible = false;
+    private boolean mUserPresent = true;
 
     public ViewFlipper(Context context) {
         super(context);
@@ -44,14 +59,62 @@
 
         TypedArray a = context.obtainStyledAttributes(attrs,
                 com.android.internal.R.styleable.ViewFlipper);
-        mFlipInterval = a.getInt(com.android.internal.R.styleable.ViewFlipper_flipInterval,
-                3000);
+        mFlipInterval = a.getInt(
+                com.android.internal.R.styleable.ViewFlipper_flipInterval, DEFAULT_INTERVAL);
+        mAutoStart = a.getBoolean(
+                com.android.internal.R.styleable.ViewFlipper_autoStart, false);
         a.recycle();
     }
 
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (Intent.ACTION_SCREEN_OFF.equals(action)) {
+                mUserPresent = false;
+                updateRunning();
+            } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
+                mUserPresent = true;
+                updateRunning();
+            }
+        }
+    };
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        // Listen for broadcasts related to user-presence
+        final IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_SCREEN_OFF);
+        filter.addAction(Intent.ACTION_USER_PRESENT);
+        getContext().registerReceiver(mReceiver, filter);
+
+        if (mAutoStart) {
+            // Automatically start when requested
+            startFlipping();
+        }
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mVisible = false;
+
+        getContext().unregisterReceiver(mReceiver);
+        updateRunning();
+    }
+
+    @Override
+    protected void onWindowVisibilityChanged(int visibility) {
+        super.onWindowVisibilityChanged(visibility);
+        mVisible = visibility == VISIBLE;
+        updateRunning();
+    }
+
     /**
      * How long to wait before flipping to the next view
-     * 
+     *
      * @param milliseconds
      *            time in milliseconds
      */
@@ -64,26 +127,61 @@
      * Start a timer to cycle through child views
      */
     public void startFlipping() {
-        if (!mKeepFlipping) {
-            mKeepFlipping = true;
-            showOnly(mWhichChild);
-            Message msg = mHandler.obtainMessage(FLIP_MSG);
-            mHandler.sendMessageDelayed(msg, mFlipInterval);
-        }
+        mStarted = true;
+        updateRunning();
     }
 
     /**
      * No more flips
      */
     public void stopFlipping() {
-        mKeepFlipping = false;
+        mStarted = false;
+        updateRunning();
+    }
+
+    /**
+     * Internal method to start or stop dispatching flip {@link Message} based
+     * on {@link #mRunning} and {@link #mVisible} state.
+     */
+    private void updateRunning() {
+        boolean running = mVisible && mStarted && mUserPresent;
+        if (running != mRunning) {
+            if (running) {
+                showOnly(mWhichChild);
+                Message msg = mHandler.obtainMessage(FLIP_MSG);
+                mHandler.sendMessageDelayed(msg, mFlipInterval);
+            } else {
+                mHandler.removeMessages(FLIP_MSG);
+            }
+            mRunning = running;
+        }
+        if (LOGD) {
+            Log.d(TAG, "updateRunning() mVisible=" + mVisible + ", mStarted=" + mStarted
+                    + ", mUserPresent=" + mUserPresent + ", mRunning=" + mRunning);
+        }
     }
 
     /**
      * Returns true if the child views are flipping.
      */
     public boolean isFlipping() {
-        return mKeepFlipping;
+        return mStarted;
+    }
+
+    /**
+     * Set if this view automatically calls {@link #startFlipping()} when it
+     * becomes attached to a window.
+     */
+    public void setAutoStart(boolean autoStart) {
+        mAutoStart = autoStart;
+    }
+
+    /**
+     * Returns true if this view automatically calls {@link #startFlipping()}
+     * when it becomes attached to a window.
+     */
+    public boolean isAutoStart() {
+        return mAutoStart;
     }
 
     private final int FLIP_MSG = 1;
@@ -92,7 +190,7 @@
         @Override
         public void handleMessage(Message msg) {
             if (msg.what == FLIP_MSG) {
-                if (mKeepFlipping) {
+                if (mRunning) {
                     showNext();
                     msg = obtainMessage(FLIP_MSG);
                     sendMessageDelayed(msg, mFlipInterval);
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index fbf4076..9d1b2c8 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2138,6 +2138,8 @@
     </declare-styleable>
     <declare-styleable name="ViewFlipper">
         <attr name="flipInterval" format="integer" min="0" />
+        <!-- When true, automatically start animating -->
+        <attr name="autoStart" format="boolean" />
     </declare-styleable>
     <declare-styleable name="ViewSwitcher">
     </declare-styleable>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 5eb1c8e..4ede620 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1201,8 +1201,8 @@
   <public type="attr" name="quickContactBadgeStyleSmallWindowSmall" />
   <public type="attr" name="quickContactBadgeStyleSmallWindowMedium" />
   <public type="attr" name="quickContactBadgeStyleSmallWindowLarge" />
-
   <public type="attr" name="wallpaperAuthor" />
   <public type="attr" name="wallpaperDescription" />
+  <public type="attr" name="autoStart" />
   
 </resources>