-> Enabled partial updates to app widgets through AppWidgetManager.
   Partial updates are not cached by the AppWidgetService.
-> Added the ability to insert commands with no parameters into
   RemoteViews objects.
-> Added showNext() and showPrevious() methods to RemoteViews.
-> Made showNext() / showPrevious() of AdapterViewFlipper remotable.

Change-Id: Ic5491bb374424a54728c4ca92b94b1f00dfb87ff
diff --git a/api/current.xml b/api/current.xml
index 297a7bc..3b262e9 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -36167,6 +36167,36 @@
 <parameter name="viewId" type="int">
 </parameter>
 </method>
+<method name="partiallyUpdateAppWidget"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="appWidgetIds" type="int[]">
+</parameter>
+<parameter name="views" type="android.widget.RemoteViews">
+</parameter>
+</method>
+<method name="partiallyUpdateAppWidget"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="appWidgetId" type="int">
+</parameter>
+<parameter name="views" type="android.widget.RemoteViews">
+</parameter>
+</method>
 <method name="updateAppWidget"
  return="void"
  abstract="false"
@@ -228211,6 +228241,32 @@
 <parameter name="visibility" type="int">
 </parameter>
 </method>
+<method name="showNext"
+ 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="showPrevious"
+ 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="writeToParcel"
  return="void"
  abstract="false"
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index 5ee721f..b83642b 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -233,6 +233,10 @@
     /**
      * Set the RemoteViews to use for the specified appWidgetIds.
      *
+     * Note that the RemoteViews parameter will be cached by the AppWidgetService, and hence should
+     * contain a complete representation of the widget. For performing partial widget updates, see
+     * {@link #partiallyUpdateAppWidget(int[], RemoteViews)}.
+     *
      * <p>
      * It is okay to call this method both inside an {@link #ACTION_APPWIDGET_UPDATE} broadcast,
      * and outside of the handler.
@@ -253,6 +257,10 @@
     /**
      * Set the RemoteViews to use for the specified appWidgetId.
      *
+     * Note that the RemoteViews parameter will be cached by the AppWidgetService, and hence should
+     * contain a complete representation of the widget. For performing partial widget updates, see
+     * {@link #partiallyUpdateAppWidget(int, RemoteViews)}.
+     *
      * <p>
      * It is okay to call this method both inside an {@link #ACTION_APPWIDGET_UPDATE} broadcast,
      * and outside of the handler.
@@ -266,6 +274,59 @@
     }
 
     /**
+     * Perform an incremental update or command on the widget(s) specified by appWidgetIds.
+     *
+     * This update  differs from {@link #updateAppWidget(int[], RemoteViews)} in that the
+     * RemoteViews object which is passed is understood to be an incomplete representation of the 
+     * widget, and hence is not cached by the AppWidgetService. Note that because these updates are 
+     * not cached, any state that they modify that is not restored by restoreInstanceState will not
+     * persist in the case that the widgets are restored using the cached version in
+     * AppWidgetService.
+     *
+     * Use with {@link RemoteViews#showNext(int)}, {@link RemoteViews#showPrevious(int)},
+     * {@link RemoteViews#setScrollPosition(int, int)} and similar commands.
+     *
+     * <p>
+     * It is okay to call this method both inside an {@link #ACTION_APPWIDGET_UPDATE} broadcast,
+     * and outside of the handler.
+     * This method will only work when called from the uid that owns the AppWidget provider.
+     *
+     * @param appWidgetIds     The AppWidget instances for which to set the RemoteViews.
+     * @param views            The RemoteViews object containing the incremental update / command.
+     */
+    public void partiallyUpdateAppWidget(int[] appWidgetIds, RemoteViews views) {
+        try {
+            sService.partiallyUpdateAppWidgetIds(appWidgetIds, views);
+        } catch (RemoteException e) {
+            throw new RuntimeException("system server dead?", e);
+        }
+    }
+
+    /**
+     * Perform an incremental update or command on the widget specified by appWidgetId.
+     *
+     * This update  differs from {@link #updateAppWidget(int, RemoteViews)} in that the RemoteViews
+     * object which is passed is understood to be an incomplete representation of the widget, and
+     * hence is not cached by the AppWidgetService. Note that because these updates are not cached,
+     * any state that they modify that is not restored by restoreInstanceState will not persist in
+     * the case that the widgets are restored using the cached version in AppWidgetService.
+     *
+     * Use with {@link RemoteViews#showNext(int)}, {@link RemoteViews#showPrevious(int)},
+     * {@link RemoteViews#setScrollPosition(int, int)} and similar commands.
+     *
+     * <p>
+     * It is okay to call this method both inside an {@link #ACTION_APPWIDGET_UPDATE} broadcast,
+     * and outside of the handler.
+     * This method will only work when called from the uid that owns the AppWidget provider.
+     *
+     * @param appWidgetId      The AppWidget instance for which to set the RemoteViews.
+     * @param views            The RemoteViews object containing the incremental update / command.
+     */
+    public void partiallyUpdateAppWidget(int appWidgetId, RemoteViews views) {
+        partiallyUpdateAppWidget(new int[] { appWidgetId }, views);
+    }
+
+    /**
      * Set the RemoteViews to use for all AppWidget instances for the supplied AppWidget provider.
      *
      * <p>
diff --git a/core/java/android/widget/AdapterViewFlipper.java b/core/java/android/widget/AdapterViewFlipper.java
index 901c761..205c0ba 100644
--- a/core/java/android/widget/AdapterViewFlipper.java
+++ b/core/java/android/widget/AdapterViewFlipper.java
@@ -25,6 +25,7 @@
 import android.os.Message;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.view.RemotableViewMethod;
 import android.view.animation.AlphaAnimation;
 import android.view.animation.Animation;
 import android.widget.RemoteViews.RemoteView;
@@ -137,6 +138,38 @@
     }
 
     /**
+     * {@inheritDoc}
+     */
+    @Override
+    @RemotableViewMethod
+    public void showNext() {
+        // if the flipper is currently flipping automatically, and showNext() is called
+        // we should we should make sure to reset the timer
+        if (mRunning) {
+            mHandler.removeMessages(FLIP_MSG);
+            Message msg = mHandler.obtainMessage(FLIP_MSG);
+            mHandler.sendMessageDelayed(msg, mFlipInterval);
+        }
+        super.showNext();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    @RemotableViewMethod
+    public void showPrevious() {
+        // if the flipper is currently flipping automatically, and showPrevious() is called
+        // we should we should make sure to reset the timer
+        if (mRunning) {
+            mHandler.removeMessages(FLIP_MSG);
+            Message msg = mHandler.obtainMessage(FLIP_MSG);
+            mHandler.sendMessageDelayed(msg, mFlipInterval);
+        }
+        super.showPrevious();
+    }
+
+    /**
      * How long to wait before flipping to the next view
      *
      * @param milliseconds
@@ -229,8 +262,6 @@
             if (msg.what == FLIP_MSG) {
                 if (mRunning) {
                     showNext();
-                    msg = obtainMessage(FLIP_MSG);
-                    sendMessageDelayed(msg, mFlipInterval);
                 }
             }
         }
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 50745dc0..f23a723 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -266,6 +266,63 @@
         public final static int TAG = 3;
     }
     
+    private class ReflectionActionWithoutParams extends Action {
+        int viewId;
+        String methodName;
+
+        public final static int TAG = 5;
+
+        ReflectionActionWithoutParams(int viewId, String methodName) {
+            this.viewId = viewId;
+            this.methodName = methodName;
+        }
+
+        ReflectionActionWithoutParams(Parcel in) {
+            this.viewId = in.readInt();
+            this.methodName = in.readString();
+        }
+
+        public void writeToParcel(Parcel out, int flags) {
+            out.writeInt(TAG);
+            out.writeInt(this.viewId);
+            out.writeString(this.methodName);
+        }
+
+        @Override
+        public void apply(View root) {
+            final View view = root.findViewById(viewId);
+            if (view == null) {
+                throw new ActionException("can't find view: 0x" + Integer.toHexString(viewId));
+            }
+
+            Class klass = view.getClass();
+            Method method;
+            try {
+                method = klass.getMethod(this.methodName);
+            } catch (NoSuchMethodException ex) {
+                throw new ActionException("view: " + klass.getName() + " doesn't have method: "
+                        + this.methodName + "()");
+            }
+
+            if (!method.isAnnotationPresent(RemotableViewMethod.class)) {
+                throw new ActionException("view: " + klass.getName()
+                        + " can't use method with RemoteViews: "
+                        + this.methodName + "()");
+            }
+
+            try {
+                //noinspection ConstantIfStatement
+                if (false) {
+                    Log.d("RemoteViews", "view: " + klass.getName() + " calling method: "
+                        + this.methodName + "()");
+                }
+                method.invoke(view);
+            } catch (Exception ex) {
+                throw new ActionException(ex);
+            }
+        }
+    }
+
     /**
      * Base class for the reflection actions.
      */
@@ -571,6 +628,9 @@
                 case ViewGroupAction.TAG:
                     mActions.add(new ViewGroupAction(parcel));
                     break;
+                case ReflectionActionWithoutParams.TAG:
+                    mActions.add(new ReflectionActionWithoutParams(parcel));
+                    break;
                 default:
                     throw new ActionException("Tag " + tag + " not found");
                 }
@@ -632,6 +692,24 @@
     }
 
     /**
+     * Equivalent to calling {@link AdapterViewFlipper#showNext()}
+     *
+     * @param viewId The id of the view on which to call {@link AdapterViewFlipper#showNext()}
+     */
+    public void showNext(int viewId) {
+        addAction(new ReflectionActionWithoutParams(viewId, "showNext"));
+    }
+
+    /**
+     * Equivalent to calling {@link AdapterViewFlipper#showPrevious()}
+     *
+     * @param viewId The id of the view on which to call {@link AdapterViewFlipper#showPrevious()}
+     */
+    public void showPrevious(int viewId) {
+        addAction(new ReflectionActionWithoutParams(viewId, "showPrevious"));
+    }
+
+    /**
      * Equivalent to calling View.setVisibility
      * 
      * @param viewId The id of the view whose visibility should change
diff --git a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
index af75d5b..8da51b1 100644
--- a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
+++ b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
@@ -40,6 +40,7 @@
     // for AppWidgetManager
     //
     void updateAppWidgetIds(in int[] appWidgetIds, in RemoteViews views);
+    void partiallyUpdateAppWidgetIds(in int[] appWidgetIds, in RemoteViews views);
     void updateAppWidgetProvider(in ComponentName provider, in RemoteViews views);
     void notifyAppWidgetViewDataChanged(in int[] appWidgetIds, in RemoteViews views, int viewId);
     List<AppWidgetProviderInfo> getInstalledProviders();
diff --git a/services/java/com/android/server/AppWidgetService.java b/services/java/com/android/server/AppWidgetService.java
index 30597320..b8b8880 100644
--- a/services/java/com/android/server/AppWidgetService.java
+++ b/services/java/com/android/server/AppWidgetService.java
@@ -425,6 +425,23 @@
         }
     }
 
+    public void partiallyUpdateAppWidgetIds(int[] appWidgetIds, RemoteViews views) {
+        if (appWidgetIds == null) {
+            return;
+        }
+        if (appWidgetIds.length == 0) {
+            return;
+        }
+        final int N = appWidgetIds.length;
+
+        synchronized (mAppWidgetIds) {
+            for (int i=0; i<N; i++) {
+                AppWidgetId id = lookupAppWidgetIdLocked(appWidgetIds[i]);
+                updateAppWidgetInstanceLocked(id, views, true);
+            }
+        }
+    }
+
     public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, RemoteViews views, int viewId) {
         if (appWidgetIds == null) {
             return;
@@ -459,11 +476,17 @@
     }
 
     void updateAppWidgetInstanceLocked(AppWidgetId id, RemoteViews views) {
+        updateAppWidgetInstanceLocked(id, views, false);
+    }
+
+    void updateAppWidgetInstanceLocked(AppWidgetId id, RemoteViews views, boolean isPartialUpdate) {
         // allow for stale appWidgetIds and other badness
         // lookup also checks that the calling process can access the appWidgetId
         // drop unbound appWidgetIds (shouldn't be possible under normal circumstances)
         if (id != null && id.provider != null && !id.provider.zombie && !id.host.zombie) {
-            id.views = views;
+
+            // We do not want to save this RemoteViews
+            if (!isPartialUpdate) id.views = views;
 
             // is anyone listening?
             if (id.host.callbacks != null) {