Implement feature #2117336: Create event communication APIs for live wallpaper

Note: currently only implements an async version (no result), and not yet
actually tested.

Change-Id: Id47ed045a4b0eb309ea8c58daf41a0e03eff1d3a
diff --git a/api/current.xml b/api/current.xml
index 6148047..8f83e39 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -24659,6 +24659,29 @@
  visibility="public"
 >
 </method>
+<method name="sendWallpaperCommand"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="windowToken" type="android.os.IBinder">
+</parameter>
+<parameter name="action" type="java.lang.String">
+</parameter>
+<parameter name="x" type="int">
+</parameter>
+<parameter name="y" type="int">
+</parameter>
+<parameter name="z" type="int">
+</parameter>
+<parameter name="extras" type="android.os.Bundle">
+</parameter>
+</method>
 <method name="setBitmap"
  return="void"
  abstract="false"
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 69c87ee..5881694 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -592,6 +592,31 @@
     }
     
     /**
+     * Send an arbitrary command to the current active wallpaper.
+     * 
+     * @param windowToken The window who these offsets should be associated
+     * with, as returned by {@link android.view.View#getWindowToken()
+     * View.getWindowToken()}.
+     * @param action Name of the command to perform.  This must be a scoped
+     * name to avoid collisions, such as "com.mycompany.wallpaper.DOIT".
+     * @param x Arbitrary integer argument based on command.
+     * @param y Arbitrary integer argument based on command.
+     * @param z Arbitrary integer argument based on command.
+     * @param extras Optional additional information for the command, or null.
+     */
+    public void sendWallpaperCommand(IBinder windowToken, String action,
+            int x, int y, int z, Bundle extras) {
+        try {
+            //Log.v(TAG, "Sending new wallpaper offsets from app...");
+            ViewRoot.getWindowSession(mContext.getMainLooper()).sendWallpaperCommand(
+                    windowToken, action, x, y, z, extras, false);
+            //Log.v(TAG, "...app returning after sending offsets!");
+        } catch (RemoteException e) {
+            // Ignore.
+        }
+    }
+    
+    /**
      * Clear the offsets previously associated with this window through
      * {@link #setWallpaperOffsets(IBinder, float, float)}.  This reverts
      * the window to its default state, where it does not cause the wallpaper
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index da8d62c0..dfd6af9 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -27,6 +27,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.graphics.Rect;
+import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
@@ -73,11 +74,21 @@
     private static final int MSG_UPDATE_SURFACE = 10000;
     private static final int MSG_VISIBILITY_CHANGED = 10010;
     private static final int MSG_WALLPAPER_OFFSETS = 10020;
+    private static final int MSG_WALLPAPER_COMMAND = 10025;
     private static final int MSG_WINDOW_RESIZED = 10030;
     private static final int MSG_TOUCH_EVENT = 10040;
     
     private Looper mCallbackLooper;
     
+    static final class WallpaperCommand {
+        String action;
+        int x;
+        int y;
+        int z;
+        Bundle extras;
+        boolean sync;
+    }
+    
     /**
      * The actual implementation of a wallpaper.  A wallpaper service may
      * have multiple instances running (for example as a real wallpaper
@@ -233,6 +244,22 @@
                 }
             }
             
+            public void dispatchWallpaperCommand(String action, int x, int y,
+                    int z, Bundle extras, boolean sync) {
+                synchronized (mLock) {
+                    if (DEBUG) Log.v(TAG, "Dispatch wallpaper command: " + x + ", " + y);
+                    WallpaperCommand cmd = new WallpaperCommand();
+                    cmd.action = action;
+                    cmd.x = x;
+                    cmd.y = y;
+                    cmd.z = z;
+                    cmd.extras = extras;
+                    cmd.sync = sync;
+                    Message msg = mCaller.obtainMessage(MSG_WALLPAPER_COMMAND);
+                    msg.obj = cmd;
+                    mCaller.sendMessage(msg);
+                }
+            }
         };
         
         /**
@@ -338,6 +365,28 @@
         }
         
         /**
+         * Process a command that was sent to the wallpaper with
+         * {@link WallpaperManager#sendWallpaperCommand(String, int, int, int, Bundle)}.
+         * The default implementation does nothing, and always returns null
+         * as the result.
+         * 
+         * @param action The name of the command to perform.  This tells you
+         * what to do and how to interpret the rest of the arguments.
+         * @param x Generic integer parameter.
+         * @param y Generic integer parameter.
+         * @param z Generic integer parameter.
+         * @param extras Any additional parameters.
+         * @param resultRequested If true, the caller is requesting that
+         * a result, appropriate for the command, be returned back.
+         * @return If returning a result, create a Bundle and place the
+         * result data in to it.  Otherwise return null.
+         */
+        public Bundle onCommand(String action, int x, int y, int z,
+                Bundle extras, boolean resultRequested) {
+            return null;
+        }
+        
+        /**
          * Called when an application has changed the desired virtual size of
          * the wallpaper.
          */
@@ -585,6 +634,23 @@
             }
         }
         
+        void doCommand(WallpaperCommand cmd) {
+            Bundle result;
+            if (!mDestroyed) {
+                result = onCommand(cmd.action, cmd.x, cmd.y, cmd.z,
+                        cmd.extras, cmd.sync);
+            } else {
+                result = null;
+            }
+            if (cmd.sync) {
+                try {
+                    if (DEBUG) Log.v(TAG, "Reporting command complete");
+                    mSession.wallpaperCommandComplete(mWindow.asBinder(), result);
+                } catch (RemoteException e) {
+                }
+            }
+        }
+        
         void detach() {
             mDestroyed = true;
             
@@ -709,6 +775,10 @@
                 case MSG_WALLPAPER_OFFSETS: {
                     mEngine.doOffsetsChanged();
                 } break;
+                case MSG_WALLPAPER_COMMAND: {
+                    WallpaperCommand cmd = (WallpaperCommand)message.obj;
+                    mEngine.doCommand(cmd);
+                } break;
                 case MSG_WINDOW_RESIZED: {
                     final boolean reportDraw = message.arg1 != 0;
                     mEngine.updateSurface(true, false);
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index 7977578..6bfc8b5 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -18,11 +18,11 @@
 package android.view;
 
 import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 
-import android.os.ParcelFileDescriptor;
-
 /**
  * API back to a client window that the Window Manager uses to inform it of
  * interesting things happening.
@@ -63,4 +63,7 @@
      * Called for wallpaper windows when their offsets change.
      */
     void dispatchWallpaperOffsets(float x, float y, boolean sync);
+    
+    void dispatchWallpaperCommand(String action, int x, int y,
+            int z, in Bundle extras, boolean sync);
 }
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 9b8b6d4..7e7a38f 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -19,6 +19,7 @@
 
 import android.graphics.Rect;
 import android.graphics.Region;
+import android.os.Bundle;
 import android.view.IWindow;
 import android.view.MotionEvent;
 import android.view.WindowManager;
@@ -116,4 +117,9 @@
     void setWallpaperPosition(IBinder windowToken, float x, float y);
     
     void wallpaperOffsetsComplete(IBinder window);
+    
+    Bundle sendWallpaperCommand(IBinder window, String action, int x, int y,
+            int z, in Bundle extras, boolean sync);
+    
+    void wallpaperCommandComplete(IBinder window, in Bundle result);
 }
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
index 3b83044..f4593f5 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -2903,6 +2903,16 @@
                 }
             }
         }
+        
+        public void dispatchWallpaperCommand(String action, int x, int y,
+                int z, Bundle extras, boolean sync) {
+            if (sync) {
+                try {
+                    sWindowSession.wallpaperCommandComplete(asBinder(), null);
+                } catch (RemoteException e) {
+                }
+            }
+        }
     }
 
     /**
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index 38ef0c2..2674262 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -1,6 +1,7 @@
 package com.android.internal.view;
 
 import android.graphics.Rect;
+import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.view.IWindow;
@@ -101,4 +102,14 @@
             }
         }
     }
+    
+    public void dispatchWallpaperCommand(String action, int x, int y,
+            int z, Bundle extras, boolean sync) {
+        if (sync) {
+            try {
+                mSession.wallpaperCommandComplete(asBinder(), null);
+            } catch (RemoteException e) {
+            }
+        }
+    }
 }
diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java
index e743513..cd6a371 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -63,6 +63,7 @@
 import android.graphics.Region;
 import android.os.BatteryStats;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.Debug;
 import android.os.Handler;
 import android.os.IBinder;
@@ -432,8 +433,6 @@
     int mWallpaperAnimLayerAdjustment;
     float mLastWallpaperX = -1;
     float mLastWallpaperY = -1;
-    // Lock for waiting for the wallpaper.
-    final Object mWaitingOnWallpaperLock = new Object();
     // This is set when we are waiting for a wallpaper to tell us it is done
     // changing its scroll position.
     WindowState mWaitingOnWallpaper;
@@ -1604,34 +1603,30 @@
                         + wallpaperWin + " x=" + wallpaperWin.mWallpaperX
                         + " y=" + wallpaperWin.mWallpaperY);
                 if (sync) {
-                    synchronized (mWaitingOnWallpaperLock) {
-                        mWaitingOnWallpaper = wallpaperWin;
-                    }
+                    mWaitingOnWallpaper = wallpaperWin;
                 }
                 wallpaperWin.mClient.dispatchWallpaperOffsets(
                         wallpaperWin.mWallpaperX, wallpaperWin.mWallpaperY, sync);
                 if (sync) {
-                    synchronized (mWaitingOnWallpaperLock) {
-                        if (mWaitingOnWallpaper != null) {
-                            long start = SystemClock.uptimeMillis();
-                            if ((mLastWallpaperTimeoutTime+WALLPAPER_TIMEOUT_RECOVERY)
-                                    < start) {
-                                try {
-                                    if (DEBUG_WALLPAPER) Log.v(TAG,
-                                            "Waiting for offset complete...");
-                                    mWaitingOnWallpaperLock.wait(WALLPAPER_TIMEOUT);
-                                } catch (InterruptedException e) {
-                                }
-                                if (DEBUG_WALLPAPER) Log.v(TAG, "Offset complete!");
-                                if ((start+WALLPAPER_TIMEOUT)
-                                        < SystemClock.uptimeMillis()) {
-                                    Log.i(TAG, "Timeout waiting for wallpaper to offset: "
-                                            + wallpaperWin);
-                                    mLastWallpaperTimeoutTime = start;
-                                }
+                    if (mWaitingOnWallpaper != null) {
+                        long start = SystemClock.uptimeMillis();
+                        if ((mLastWallpaperTimeoutTime+WALLPAPER_TIMEOUT_RECOVERY)
+                                < start) {
+                            try {
+                                if (DEBUG_WALLPAPER) Log.v(TAG,
+                                        "Waiting for offset complete...");
+                                mWindowMap.wait(WALLPAPER_TIMEOUT);
+                            } catch (InterruptedException e) {
                             }
-                            mWaitingOnWallpaper = null;
+                            if (DEBUG_WALLPAPER) Log.v(TAG, "Offset complete!");
+                            if ((start+WALLPAPER_TIMEOUT)
+                                    < SystemClock.uptimeMillis()) {
+                                Log.i(TAG, "Timeout waiting for wallpaper to offset: "
+                                        + wallpaperWin);
+                                mLastWallpaperTimeoutTime = start;
+                            }
                         }
+                        mWaitingOnWallpaper = null;
                     }
                 }
             } catch (RemoteException e) {
@@ -1642,11 +1637,11 @@
     }
     
     void wallpaperOffsetsComplete(IBinder window) {
-        synchronized (mWaitingOnWallpaperLock) {
+        synchronized (mWindowMap) {
             if (mWaitingOnWallpaper != null &&
                     mWaitingOnWallpaper.mClient.asBinder() == window) {
                 mWaitingOnWallpaper = null;
-                mWaitingOnWallpaperLock.notifyAll();
+                mWindowMap.notifyAll();
             }
         }
     }
@@ -2196,6 +2191,47 @@
         }
     }
     
+    void wallpaperCommandComplete(IBinder window, Bundle result) {
+        synchronized (mWindowMap) {
+            if (mWaitingOnWallpaper != null &&
+                    mWaitingOnWallpaper.mClient.asBinder() == window) {
+                mWaitingOnWallpaper = null;
+                mWindowMap.notifyAll();
+            }
+        }
+    }
+    
+    public Bundle sendWindowWallpaperCommandLocked(WindowState window,
+            String action, int x, int y, int z, Bundle extras, boolean sync) {
+        if (window == mWallpaperTarget || window == mLowerWallpaperTarget
+                || window == mUpperWallpaperTarget) {
+            boolean doWait = sync;
+            int curTokenIndex = mWallpaperTokens.size();
+            while (curTokenIndex > 0) {
+                curTokenIndex--;
+                WindowToken token = mWallpaperTokens.get(curTokenIndex);
+                int curWallpaperIndex = token.windows.size();
+                while (curWallpaperIndex > 0) {
+                    curWallpaperIndex--;
+                    WindowState wallpaper = token.windows.get(curWallpaperIndex);
+                    try {
+                        wallpaper.mClient.dispatchWallpaperCommand(action,
+                                x, y, z, extras, sync);
+                        // We only want to be synchronous with one wallpaper.
+                        sync = false;
+                    } catch (RemoteException e) {
+                    }
+                }
+            }
+            
+            if (doWait) {
+                // XXX Need to wait for result.
+            }
+        }
+        
+        return null;
+    }
+    
     public int relayoutWindow(Session session, IWindow client,
             WindowManager.LayoutParams attrs, int requestedWidth,
             int requestedHeight, int viewVisibility, boolean insetsPending,
@@ -6565,6 +6601,24 @@
             WindowManagerService.this.wallpaperOffsetsComplete(window);
         }
         
+        public Bundle sendWallpaperCommand(IBinder window, String action, int x, int y,
+                int z, Bundle extras, boolean sync) {
+            synchronized(mWindowMap) {
+                long ident = Binder.clearCallingIdentity();
+                try {
+                    return sendWindowWallpaperCommandLocked(
+                            windowForClientLocked(this, window),
+                            action, x, y, z, extras, sync);
+                } finally {
+                    Binder.restoreCallingIdentity(ident);
+                }
+            }
+        }
+        
+        public void wallpaperCommandComplete(IBinder window, Bundle result) {
+            WindowManagerService.this.wallpaperCommandComplete(window, result);
+        }
+        
         void windowAddedLocked() {
             if (mSurfaceSession == null) {
                 if (localLOGV) Log.v(
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
index d28a151..f0223e8 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
@@ -36,6 +36,7 @@
 import android.graphics.Region;
 import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -1069,6 +1070,18 @@
         }
         
         @SuppressWarnings("unused")
+        public Bundle sendWallpaperCommand(IBinder window, String action, int x, int y,
+                int z, Bundle extras, boolean sync) {
+            // pass for now.
+            return null;
+        }
+        
+        @SuppressWarnings("unused")
+        public void wallpaperCommandComplete(IBinder window, Bundle result) {
+            // pass for now.
+        }
+        
+        @SuppressWarnings("unused")
         public void closeSystemDialogs(String reason) {
             // pass for now.
         }
@@ -1132,6 +1145,12 @@
         }
 
         @SuppressWarnings("unused")
+        public void dispatchWallpaperCommand(String action, int x, int y,
+                int z, Bundle extras, boolean sync) {
+            // pass for now.
+        }
+        
+        @SuppressWarnings("unused")
         public void closeSystemDialogs(String reason) {
             // pass for now.
         }