Keyguard wallpaper

Clients can now set a lock-only wallpaper that Keyguard can
observe and choose to draw as appropriate.

Bug 25454162

Change-Id: I3fc30e02919e814b55dfded2a1a36ad9d2e55299
diff --git a/api/current.txt b/api/current.txt
index 96900cd..f1f823a 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5721,6 +5721,7 @@
     method public android.graphics.drawable.Drawable getDrawable();
     method public android.graphics.drawable.Drawable getFastDrawable();
     method public static android.app.WallpaperManager getInstance(android.content.Context);
+    method public android.os.ParcelFileDescriptor getWallpaperFile(int);
     method public android.app.WallpaperInfo getWallpaperInfo();
     method public boolean hasResourceWallpaper(int);
     method public boolean isWallpaperSettingAllowed();
diff --git a/api/system-current.txt b/api/system-current.txt
index 8c4e3dc..f326368 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5844,6 +5844,7 @@
   public class WallpaperManager {
     method public void clear() throws java.io.IOException;
     method public void clearWallpaper();
+    method public void clearWallpaper(int, int);
     method public void clearWallpaperOffsets(android.os.IBinder);
     method public void forgetLoadedWallpaper();
     method public android.graphics.drawable.Drawable getBuiltInDrawable();
@@ -5854,6 +5855,7 @@
     method public android.graphics.drawable.Drawable getDrawable();
     method public android.graphics.drawable.Drawable getFastDrawable();
     method public static android.app.WallpaperManager getInstance(android.content.Context);
+    method public android.os.ParcelFileDescriptor getWallpaperFile(int);
     method public android.app.WallpaperInfo getWallpaperInfo();
     method public boolean hasResourceWallpaper(int);
     method public boolean isWallpaperSettingAllowed();
diff --git a/api/test-current.txt b/api/test-current.txt
index 68c51a5..2a66162 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -5723,6 +5723,7 @@
     method public android.graphics.drawable.Drawable getDrawable();
     method public android.graphics.drawable.Drawable getFastDrawable();
     method public static android.app.WallpaperManager getInstance(android.content.Context);
+    method public android.os.ParcelFileDescriptor getWallpaperFile(int);
     method public android.app.WallpaperInfo getWallpaperInfo();
     method public boolean hasResourceWallpaper(int);
     method public boolean isWallpaperSettingAllowed();
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index 04493cb..1143c6a 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -27,7 +27,7 @@
 interface IWallpaperManager {
 
     /**
-     * Set the wallpaper.
+     * Set the wallpaper for the current user.
      *
      * If 'extras' is non-null, on successful return it will contain:
      *   EXTRA_SET_WALLPAPER_ID : integer ID that the new wallpaper will have
@@ -56,10 +56,10 @@
     void setWallpaperComponent(in ComponentName name);
 
     /**
-     * Get the system wallpaper.
+     * Get the wallpaper for a given user.
      */
-    ParcelFileDescriptor getWallpaper(IWallpaperManagerCallback cb,
-            out Bundle outParams);
+    ParcelFileDescriptor getWallpaper(IWallpaperManagerCallback cb, int which,
+            out Bundle outParams, int userId);
 
     /**
      * If the current system wallpaper is a live wallpaper component, return the
@@ -71,7 +71,7 @@
     /**
      * Clear the system wallpaper.
      */
-    void clearWallpaper(in String callingPackage);
+    void clearWallpaper(in String callingPackage, int which, int userId);
 
     /**
      * Return whether the current system wallpaper has the given name.
@@ -118,4 +118,10 @@
      * Check whether setting of wallpapers are allowed for the calling user.
      */
     boolean isWallpaperSettingAllowed(in String callingPackage);
+
+    /*
+     * Keyguard: register a callback for being notified that lock-state relevant
+     * wallpaper content has changed.
+     */
+    boolean setLockWallpaperCallback(IWallpaperManagerCallback cb);
 }
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index eda82c0..1875e61 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2009 The Android Open Source Project
+h * Copyright (C) 2009 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.
@@ -49,6 +49,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemProperties;
+import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.WindowManagerGlobal;
@@ -265,11 +266,10 @@
         private Bitmap mWallpaper;
         private Bitmap mDefaultWallpaper;
 
-        private static final int MSG_CLEAR_WALLPAPER = 1;
-
         Globals(Looper looper) {
             IBinder b = ServiceManager.getService(Context.WALLPAPER_SERVICE);
             mService = IWallpaperManager.Stub.asInterface(b);
+            forgetLoadedWallpaper();
         }
 
         public void onWallpaperChanged() {
@@ -278,10 +278,7 @@
              * to null so if the user requests the wallpaper again then we'll
              * fetch it.
              */
-            synchronized (this) {
-                mWallpaper = null;
-                mDefaultWallpaper = null;
-            }
+            forgetLoadedWallpaper();
         }
 
         public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault) {
@@ -334,7 +331,8 @@
 
             try {
                 Bundle params = new Bundle();
-                ParcelFileDescriptor fd = mService.getWallpaper(this, params);
+                ParcelFileDescriptor fd = mService.getWallpaper(this, FLAG_SET_SYSTEM,
+                        params, context.getUserId());
                 if (fd != null) {
                     try {
                         BitmapFactory.Options options = new BitmapFactory.Options();
@@ -633,7 +631,7 @@
      * wallpaper or a null pointer if these is none.
      */
     public Drawable peekFastDrawable() {
-        Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false);
+       Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false);
         if (bm != null) {
             return new FastBitmapDrawable(bm);
         }
@@ -650,15 +648,49 @@
     }
 
     /**
+     * Get an open, readable file descriptor to the given wallpaper image file.
+     * The callee is resopnsible for closing the fd when done ingesting the file.
+     *
+     * <p>If no lock-specific wallpaper has been configured for the given user, then
+     * this method will return {@code null} when requesting {@link #FLAG_SET_LOCK} rather than
+     * returning the system wallpaper's image file.
+     */
+    public ParcelFileDescriptor getWallpaperFile(int which) {
+        return getWallpaperFile(which, mContext.getUserId());
+    }
+
+    /**
+     * Version of {@link #getWallpaperFile(int)} that can access the wallpaper data
+     * for a given user.  The caller must hold the INTERACT_ACROSS_USERS_FULL
+     * permission to access another user's wallpaper data.
+     * @hide
+     */
+    public ParcelFileDescriptor getWallpaperFile(int which, int userId) {
+        if (which != FLAG_SET_SYSTEM && which != FLAG_SET_LOCK) {
+            throw new IllegalArgumentException("Must request exactly one kind of wallpaper");
+        }
+
+        if (sGlobals.mService == null) {
+            Log.w(TAG, "WallpaperService not running");
+            return null;
+        } else {
+            try {
+                Bundle outParams = new Bundle();
+                return sGlobals.mService.getWallpaper(null, which, outParams, userId);
+            } catch (RemoteException e) {
+                return null;
+            }
+        }
+    }
+
+    /**
      * Remove all internal references to the last loaded wallpaper.  Useful
      * for apps that want to reduce memory usage when they only temporarily
      * need to have the wallpaper.  After calling, the next request for the
      * wallpaper will require reloading it again from disk.
      */
     public void forgetLoadedWallpaper() {
-        if (isWallpaperSupported()) {
-            sGlobals.forgetLoadedWallpaper();
-        }
+        sGlobals.forgetLoadedWallpaper();
     }
 
     /**
@@ -1209,12 +1241,23 @@
      */
     @SystemApi
     public void clearWallpaper() {
+        clearWallpaper(FLAG_SET_SYSTEM, mContext.getUserId());
+    }
+
+    /**
+     * Clear the wallpaper for a specific user.  The caller must hold the
+     * INTERACT_ACROSS_USERS_FULL permission to clear another user's
+     * wallpaper.
+     * @hide
+     */
+    @SystemApi
+    public void clearWallpaper(int which, int userId) {
         if (sGlobals.mService == null) {
             Log.w(TAG, "WallpaperService not running");
             return;
         }
         try {
-            sGlobals.mService.clearWallpaper(mContext.getOpPackageName());
+            sGlobals.mService.clearWallpaper(mContext.getOpPackageName(), which, userId);
         } catch (RemoteException e) {
             // Ignore
         }
@@ -1363,7 +1406,7 @@
     }
 
     /**
-     * Remove any currently set wallpaper, reverting to the system's built-in
+     * Remove any currently set system wallpaper, reverting to the system's built-in
      * wallpaper. On success, the intent {@link Intent#ACTION_WALLPAPER_CHANGED}
      * is broadcast.
      *
@@ -1424,6 +1467,26 @@
         return null;
     }
 
+    /**
+     * Register a callback for lock wallpaper observation. Only the OS may use this.
+     *
+     * @return true on success; false on error.
+     * @hide
+     */
+    public boolean setLockWallpaperCallback(IWallpaperManagerCallback callback) {
+        if (sGlobals.mService == null) {
+            Log.w(TAG, "WallpaperService not running");
+            return false;
+        }
+
+        try {
+            return sGlobals.mService.setLockWallpaperCallback(callback);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Unable to contact wallpaper service");
+        }
+        return false;
+    }
+
     // Private completion callback for setWallpaper() synchronization
     private class WallpaperSetCompletion extends IWallpaperManagerCallback.Stub {
         final CountDownLatch mLatch;
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 09c53ae..7387f02 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -16,6 +16,8 @@
 
 package com.android.server.wallpaper;
 
+import static android.app.WallpaperManager.FLAG_SET_SYSTEM;
+import static android.app.WallpaperManager.FLAG_SET_LOCK;
 import static android.os.ParcelFileDescriptor.*;
 
 import android.app.ActivityManagerNative;
@@ -104,7 +106,7 @@
     static final String TAG = "WallpaperManagerService";
     static final boolean DEBUG = true;
 
-    final Object mLock = new Object[0];
+    final Object mLock = new Object();
 
     /**
      * Minimum time between crashes of a wallpaper service for us to consider
@@ -114,8 +116,17 @@
     static final int MAX_WALLPAPER_COMPONENT_LOG_LENGTH = 128;
     static final String WALLPAPER = "wallpaper_orig";
     static final String WALLPAPER_CROP = "wallpaper";
+    static final String WALLPAPER_LOCK_ORIG = "wallpaper_lock_orig";
+    static final String WALLPAPER_LOCK_CROP = "wallpaper_lock";
     static final String WALLPAPER_INFO = "wallpaper_info.xml";
 
+    // All the various per-user state files we need to be aware of
+    static final String[] sPerUserFiles = new String[] {
+        WALLPAPER, WALLPAPER_CROP,
+        WALLPAPER_LOCK_ORIG, WALLPAPER_LOCK_CROP,
+        WALLPAPER_INFO
+    };
+
     /**
      * Observes the wallpaper for changes and notifies all IWallpaperServiceCallbacks
      * that the wallpaper has changed. The CREATE is triggered when there is no
@@ -124,22 +135,38 @@
      */
     private class WallpaperObserver extends FileObserver {
 
+        final int mUserId;
         final WallpaperData mWallpaper;
         final File mWallpaperDir;
         final File mWallpaperFile;
-        final File mWallpaperCropFile;
+        final File mWallpaperLockFile;
         final File mWallpaperInfoFile;
 
         public WallpaperObserver(WallpaperData wallpaper) {
             super(getWallpaperDir(wallpaper.userId).getAbsolutePath(),
                     CLOSE_WRITE | MOVED_TO | DELETE | DELETE_SELF);
+            mUserId = wallpaper.userId;
             mWallpaperDir = getWallpaperDir(wallpaper.userId);
             mWallpaper = wallpaper;
             mWallpaperFile = new File(mWallpaperDir, WALLPAPER);
-            mWallpaperCropFile = new File(mWallpaperDir, WALLPAPER_CROP);
+            mWallpaperLockFile = new File(mWallpaperDir, WALLPAPER_LOCK_ORIG);
             mWallpaperInfoFile = new File(mWallpaperDir, WALLPAPER_INFO);
         }
 
+        private WallpaperData dataForEvent(boolean sysChanged, boolean lockChanged) {
+            WallpaperData wallpaper = null;
+            synchronized (mLock) {
+                if (lockChanged) {
+                    wallpaper = mLockWallpaperMap.get(mUserId);
+                }
+                if (wallpaper == null) {
+                    // no lock-specific wallpaper exists, or sys case, handled together
+                    wallpaper = mWallpaperMap.get(mUserId);
+                }
+            }
+            return (wallpaper != null) ? wallpaper : mWallpaper;
+        }
+
         @Override
         public void onEvent(int event, String path) {
             if (path == null) {
@@ -148,38 +175,77 @@
             final boolean written = (event == CLOSE_WRITE || event == MOVED_TO);
             final File changedFile = new File(mWallpaperDir, path);
 
+            // System and system+lock changes happen on the system wallpaper input file;
+            // lock-only changes happen on the dedicated lock wallpaper input file
+            final boolean sysWallpaperChanged = (mWallpaperFile.equals(changedFile));
+            final boolean lockWallpaperChanged = (mWallpaperLockFile.equals(changedFile));
+            WallpaperData wallpaper = dataForEvent(sysWallpaperChanged, lockWallpaperChanged);
+
+            if (DEBUG) {
+                Slog.v(TAG, "Wallpaper file change: evt=" + event
+                        + " path=" + path
+                        + " sys=" + sysWallpaperChanged
+                        + " lock=" + lockWallpaperChanged
+                        + " imagePending=" + wallpaper.imageWallpaperPending
+                        + " whichPending=0x" + Integer.toHexString(wallpaper.whichPending)
+                        + " written=" + written);
+            }
             synchronized (mLock) {
-                if (mWallpaperFile.equals(changedFile)
-                        || mWallpaperInfoFile.equals(changedFile)) {
+                if (sysWallpaperChanged || mWallpaperInfoFile.equals(changedFile)) {
                     // changing the wallpaper means we'll need to back up the new one
                     long origId = Binder.clearCallingIdentity();
                     BackupManager bm = new BackupManager(mContext);
                     bm.dataChanged();
                     Binder.restoreCallingIdentity(origId);
                 }
-                if (mWallpaperFile.equals(changedFile)) {
-                    notifyCallbacksLocked(mWallpaper);
-                    if (mWallpaper.wallpaperComponent == null
+                if (sysWallpaperChanged || lockWallpaperChanged) {
+                    notifyCallbacksLocked(wallpaper);
+                    if (wallpaper.wallpaperComponent == null
                             || event != CLOSE_WRITE // includes the MOVED_TO case
-                            || mWallpaper.imageWallpaperPending) {
+                            || wallpaper.imageWallpaperPending) {
                         if (written) {
                             // The image source has finished writing the source image,
                             // so we now produce the crop rect (in the background), and
                             // only publish the new displayable (sub)image as a result
                             // of that work.
-                            generateCrop(mWallpaper);
-                            mWallpaper.imageWallpaperPending = false;
-                            if (mWallpaper.setComplete != null) {
+                            if (DEBUG) {
+                                Slog.v(TAG, "Wallpaper written; generating crop");
+                            }
+                            generateCrop(wallpaper);
+                            if (DEBUG) {
+                                Slog.v(TAG, "Crop done; invoking completion callback");
+                            }
+                            wallpaper.imageWallpaperPending = false;
+                            if (wallpaper.setComplete != null) {
                                 try {
-                                    mWallpaper.setComplete.onWallpaperChanged();
+                                    wallpaper.setComplete.onWallpaperChanged();
                                 } catch (RemoteException e) {
                                     // if this fails we don't really care; the setting app may just
                                     // have crashed and that sort of thing is a fact of life.
                                 }
                             }
-                            bindWallpaperComponentLocked(mImageWallpaper, true,
-                                    false, mWallpaper, null);
-                            saveSettingsLocked(mWallpaper);
+                            if (sysWallpaperChanged) {
+                                // If this was the system wallpaper, rebind...
+                                bindWallpaperComponentLocked(mImageWallpaper, true,
+                                        false, wallpaper, null);
+                            }
+                            if (lockWallpaperChanged
+                                    || (wallpaper.whichPending & FLAG_SET_LOCK) != 0) {
+                                // either a lock-only wallpaper commit or a system+lock event,
+                                // so tell keyguard about it
+                                if (DEBUG) {
+                                    Slog.i(TAG, "Lock-relevant wallpaper changed; telling listener");
+                                }
+                                final IWallpaperManagerCallback cb = mKeyguardListener;
+                                if (cb != null) {
+                                    try {
+                                        cb.onWallpaperChanged();
+                                    } catch (RemoteException e) {
+                                        // Oh well it went away; no big deal
+                                    }
+                                }
+                            }
+                            saveSettingsLocked(wallpaper);
                         }
                     }
                 }
@@ -194,12 +260,21 @@
     private void generateCrop(WallpaperData wallpaper) {
         boolean success = false;
         boolean needCrop = false;
+        boolean needScale = false;
+
+        if (DEBUG) {
+            Slog.v(TAG, "Generating crop for new wallpaper(s): 0x"
+                    + Integer.toHexString(wallpaper.whichPending)
+                    + " to " + wallpaper.cropFile.getName());
+        }
 
         // Analyse the source; needed in multiple cases
         BitmapFactory.Options options = new BitmapFactory.Options();
         options.inJustDecodeBounds = true;
         BitmapFactory.decodeFile(wallpaper.wallpaperFile.getAbsolutePath(), options);
 
+        // We'll need to scale if the crop is sufficiently bigger than the display
+
         // Legacy case uses an empty crop rect here, so we just preserve the
         // source image verbatim
         if (!wallpaper.cropHint.isEmpty()) {
@@ -211,7 +286,7 @@
                     && options.outWidth >= wallpaper.cropHint.width());
         }
 
-        if (!needCrop) {
+        if (!needCrop && !needScale) {
             // Simple case:  the nominal crop is at least as big as the source image,
             // so we take the whole thing and just copy the image file directly.
             if (DEBUG) {
@@ -223,7 +298,7 @@
                 // TODO: fall back to default wallpaper in this case
             }
         } else {
-            // Fancy case: the crop is a subrect of the source
+            // Fancy case: crop and/or scale
             FileOutputStream f = null;
             BufferedOutputStream bos = null;
             try {
@@ -270,6 +345,7 @@
     final MyPackageMonitor mMonitor;
     final AppOpsManager mAppOpsManager;
     WallpaperData mLastWallpaper;
+    IWallpaperManagerCallback mKeyguardListener;
 
     /**
      * ID of the current wallpaper, changed every time anything sets a wallpaper.
@@ -283,7 +359,8 @@
      */
     final ComponentName mImageWallpaper;
 
-    SparseArray<WallpaperData> mWallpaperMap = new SparseArray<WallpaperData>();
+    final SparseArray<WallpaperData> mWallpaperMap = new SparseArray<WallpaperData>();
+    final SparseArray<WallpaperData> mLockWallpaperMap = new SparseArray<WallpaperData>();
 
     int mCurrentUserId;
 
@@ -291,15 +368,21 @@
 
         int userId;
 
-        final File wallpaperFile;
-        final File cropFile;
+        final File wallpaperFile;   // source image
+        final File cropFile;        // eventual destination
 
         /**
-         * Client is currently writing a new image wallpaper.
+         * True while the client is writing a new wallpaper
          */
         boolean imageWallpaperPending;
 
         /**
+         * Which new wallpapers are being written; mirrors the 'which'
+         * selector bit field to setWallpaper().
+         */
+        int whichPending;
+
+        /**
          * Callback once the set + crop is finished
          */
         IWallpaperManagerCallback setComplete;
@@ -345,13 +428,14 @@
 
         final Rect padding = new Rect(0, 0, 0, 0);
 
-        WallpaperData(int userId) {
+        WallpaperData(int userId, String inputFileName, String cropFileName) {
             this.userId = userId;
-            wallpaperFile = new File(getWallpaperDir(userId), WALLPAPER);
-            cropFile = new File(getWallpaperDir(userId), WALLPAPER_CROP);
+            final File wallpaperDir = getWallpaperDir(userId);
+            wallpaperFile = new File(wallpaperDir, inputFileName);
+            cropFile = new File(wallpaperDir, cropFileName);
         }
 
-        // Only called in single-threaded boot sequence mode
+        // Called during initialization of a given user's wallpaper bookkeeping
         boolean ensureCropExists() {
             // if the crop file is not present, copy over the source image to use verbatim
             if (!cropFile.exists()) {
@@ -419,7 +503,7 @@
                                 && mWallpaper.lastDiedTime + MIN_WALLPAPER_CRASH_TIME
                                     > SystemClock.uptimeMillis()) {
                             Slog.w(TAG, "Reverting to built-in wallpaper!");
-                            clearWallpaperLocked(true, mWallpaper.userId, null);
+                            clearWallpaperLocked(true, FLAG_SET_SYSTEM, mWallpaper.userId, null);
                         } else {
                             mWallpaper.lastDiedTime = SystemClock.uptimeMillis();
                         }
@@ -498,7 +582,7 @@
                         if (!bindWallpaperComponentLocked(comp, false, false,
                                 wallpaper, null)) {
                             Slog.w(TAG, "Wallpaper no longer available; reverting to default");
-                            clearWallpaperLocked(false, wallpaper.userId, null);
+                            clearWallpaperLocked(false, FLAG_SET_SYSTEM, wallpaper.userId, null);
                         }
                     }
                 }
@@ -578,7 +662,7 @@
                     if (doit) {
                         Slog.w(TAG, "Wallpaper uninstalled, removing: "
                                 + wallpaper.wallpaperComponent);
-                        clearWallpaperLocked(false, wallpaper.userId, null);
+                        clearWallpaperLocked(false, FLAG_SET_SYSTEM, wallpaper.userId, null);
                     }
                 }
             }
@@ -598,7 +682,7 @@
                 } catch (NameNotFoundException e) {
                     Slog.w(TAG, "Wallpaper component gone, removing: "
                             + wallpaper.wallpaperComponent);
-                    clearWallpaperLocked(false, wallpaper.userId, null);
+                    clearWallpaperLocked(false, FLAG_SET_SYSTEM, wallpaper.userId, null);
                 }
             }
             if (wallpaper.nextWallpaperComponent != null
@@ -646,7 +730,7 @@
         if (DEBUG) Slog.v(TAG, "systemReady");
         WallpaperData wallpaper = mWallpaperMap.get(UserHandle.USER_SYSTEM);
         if (!wallpaper.ensureCropExists()) {
-            clearWallpaperLocked(false, UserHandle.USER_SYSTEM, null);
+            clearWallpaperLocked(false, FLAG_SET_SYSTEM, UserHandle.USER_SYSTEM, null);
         }
         switchWallpaper(wallpaper, null);
         wallpaper.wallpaperObserver = new WallpaperObserver(wallpaper);
@@ -706,37 +790,38 @@
         }
     }
 
-    void onStoppingUser(int userId) {
-        if (userId < 1) return;
-        synchronized (mLock) {
-            WallpaperData wallpaper = mWallpaperMap.get(userId);
-            if (wallpaper != null) {
-                if (wallpaper.wallpaperObserver != null) {
-                    wallpaper.wallpaperObserver.stopWatching();
-                    wallpaper.wallpaperObserver = null;
-                }
-                mWallpaperMap.remove(userId);
+    void stopObserver(WallpaperData wallpaper) {
+        if (wallpaper != null) {
+            if (wallpaper.wallpaperObserver != null) {
+                wallpaper.wallpaperObserver.stopWatching();
+                wallpaper.wallpaperObserver = null;
             }
         }
     }
 
+    void stopObserversLocked(int userId) {
+        stopObserver(mWallpaperMap.get(userId));
+        stopObserver(mLockWallpaperMap.get(userId));
+        mWallpaperMap.remove(userId);
+        mLockWallpaperMap.remove(userId);
+    }
+
     void onRemoveUser(int userId) {
         if (userId < 1) return;
+
+        final File wallpaperDir = getWallpaperDir(userId);
         synchronized (mLock) {
-            onStoppingUser(userId);
-            File wallpaperFile = new File(getWallpaperDir(userId), WALLPAPER);
-            wallpaperFile.delete();
-            File cropFile = new File(getWallpaperDir(userId), WALLPAPER_CROP);
-            cropFile.delete();
-            File wallpaperInfoFile = new File(getWallpaperDir(userId), WALLPAPER_INFO);
-            wallpaperInfoFile.delete();
+            stopObserversLocked(userId);
+            for (String filename : sPerUserFiles) {
+                new File(wallpaperDir, filename).delete();
+            }
         }
     }
 
     void switchUser(int userId, IRemoteCallback reply) {
         synchronized (mLock) {
             mCurrentUserId = userId;
-            WallpaperData wallpaper = getWallpaperSafeLocked(userId);
+            WallpaperData wallpaper = getWallpaperSafeLocked(userId, FLAG_SET_SYSTEM);
             // Not started watching yet, in case wallpaper data was loaded for other reasons.
             if (wallpaper.wallpaperObserver == null) {
                 wallpaper.wallpaperObserver = new WallpaperObserver(wallpaper);
@@ -759,32 +844,71 @@
                 e = e1;
             }
             Slog.w(TAG, "Failure starting previous wallpaper", e);
-            clearWallpaperLocked(false, wallpaper.userId, reply);
+            clearWallpaperLocked(false, FLAG_SET_SYSTEM, wallpaper.userId, reply);
         }
     }
 
-    public void clearWallpaper(String callingPackage) {
+    @Override
+    public void clearWallpaper(String callingPackage, int which, int userId) {
         if (DEBUG) Slog.v(TAG, "clearWallpaper");
         checkPermission(android.Manifest.permission.SET_WALLPAPER);
         if (!isWallpaperSupported(callingPackage) || !isWallpaperSettingAllowed(callingPackage)) {
             return;
         }
+        if (userId != UserHandle.getCallingUserId()) {
+            // cross-user call
+            mContext.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                    "WallpaperManagerService");
+        }
+
         synchronized (mLock) {
-            clearWallpaperLocked(false, UserHandle.getCallingUserId(), null);
+            clearWallpaperLocked(false, which, userId, null);
         }
     }
 
-    void clearWallpaperLocked(boolean defaultFailed, int userId, IRemoteCallback reply) {
-        WallpaperData wallpaper = mWallpaperMap.get(userId);
+    void clearWallpaperLocked(boolean defaultFailed, int which, int userId, IRemoteCallback reply) {
+        if (which != FLAG_SET_SYSTEM && which != FLAG_SET_LOCK) {
+            throw new IllegalArgumentException("Must specify exactly one kind of wallpaper to read");
+        }
+
+        WallpaperData wallpaper = null;
+        if (which == FLAG_SET_LOCK) {
+            wallpaper = mLockWallpaperMap.get(userId);
+            if (wallpaper == null) {
+                // It's already gone; we're done.
+                return;
+            }
+        } else {
+            wallpaper = mWallpaperMap.get(userId);
+            if (wallpaper == null) {
+                // Might need to bring it in the first time to establish our rewrite
+                loadSettingsLocked(userId);
+                wallpaper = mWallpaperMap.get(userId);
+            }
+        }
         if (wallpaper == null) {
             return;
         }
-        if (wallpaper.wallpaperFile.exists()) {
-            wallpaper.wallpaperFile.delete();
-            wallpaper.cropFile.delete();
-        }
+
         final long ident = Binder.clearCallingIdentity();
         try {
+            if (wallpaper.wallpaperFile.exists()) {
+                wallpaper.wallpaperFile.delete();
+                wallpaper.cropFile.delete();
+                if (which == FLAG_SET_LOCK) {
+                    final IWallpaperManagerCallback cb = mKeyguardListener;
+                    if (cb != null) {
+                        try {
+                            cb.onWallpaperChanged();
+                        } catch (RemoteException e) {
+                            // Oh well it went away; no big deal
+                        }
+                    }
+                    return;
+                }
+            }
+
             RuntimeException e = null;
             try {
                 wallpaper.imageWallpaperPending = false;
@@ -859,7 +983,7 @@
         }
         synchronized (mLock) {
             int userId = UserHandle.getCallingUserId();
-            WallpaperData wallpaper = getWallpaperSafeLocked(userId);
+            WallpaperData wallpaper = getWallpaperSafeLocked(userId, FLAG_SET_SYSTEM);
             if (width <= 0 || height <= 0) {
                 throw new IllegalArgumentException("width and height must be > 0");
             }
@@ -921,7 +1045,7 @@
         }
         synchronized (mLock) {
             int userId = UserHandle.getCallingUserId();
-            WallpaperData wallpaper = getWallpaperSafeLocked(userId);
+            WallpaperData wallpaper = getWallpaperSafeLocked(userId, FLAG_SET_SYSTEM);
             if (padding.left < 0 || padding.top < 0 || padding.right < 0 || padding.bottom < 0) {
                 throw new IllegalArgumentException("padding must be positive: " + padding);
             }
@@ -948,19 +1072,31 @@
         }
     }
 
-    public ParcelFileDescriptor getWallpaper(IWallpaperManagerCallback cb,
-            Bundle outParams) {
+    public ParcelFileDescriptor getWallpaper(IWallpaperManagerCallback cb, final int which,
+            Bundle outParams, int wallpaperUserId) {
+        if (wallpaperUserId != UserHandle.getCallingUserId()) {
+            // cross-user call
+            mContext.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                    "WallpaperManagerService");
+        }
+
+        if (which != FLAG_SET_SYSTEM && which != FLAG_SET_LOCK) {
+            throw new IllegalArgumentException("Must specify exactly one kind of wallpaper to read");
+        }
+
         synchronized (mLock) {
-            // This returns the current user's wallpaper, if called by a system service. Else it
-            // returns the wallpaper for the calling user.
-            int callingUid = Binder.getCallingUid();
-            int wallpaperUserId = 0;
-            if (callingUid == android.os.Process.SYSTEM_UID) {
-                wallpaperUserId = mCurrentUserId;
+            WallpaperData wallpaper = null;
+            if (which == FLAG_SET_LOCK) {
+                wallpaper = mLockWallpaperMap.get(wallpaperUserId);
+                if (wallpaper == null) {
+                    // If you ask for the lock wallpaper specifically and there isn't one,
+                    // we say so rather than returning the system wallpaper as fallback.
+                    return null;
+                }
             } else {
-                wallpaperUserId = UserHandle.getUserId(callingUid);
+                wallpaper = mWallpaperMap.get(wallpaperUserId);
             }
-            WallpaperData wallpaper = mWallpaperMap.get(wallpaperUserId);
             if (wallpaper == null) {
                 return null;
             }
@@ -994,11 +1130,21 @@
     }
 
     @Override
+    public boolean setLockWallpaperCallback(IWallpaperManagerCallback cb) {
+        checkPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW);
+        synchronized (mLock) {
+            mKeyguardListener = cb;
+        }
+        return true;
+    }
+
+    @Override
     public ParcelFileDescriptor setWallpaper(String name, String callingPackage,
             Rect cropHint, Bundle extras, int which, IWallpaperManagerCallback completion) {
         checkPermission(android.Manifest.permission.SET_WALLPAPER);
 
-        if (which == 0) {
+        if ((which & (FLAG_SET_LOCK|FLAG_SET_SYSTEM)) == 0) {
+            Slog.e(TAG, "Must specify a valid wallpaper category to set");
             return null;
         }
 
@@ -1017,15 +1163,19 @@
             }
         }
 
+        final int userId = UserHandle.getCallingUserId();
+
         synchronized (mLock) {
-            if (DEBUG) Slog.v(TAG, "setWallpaper");
-            int userId = UserHandle.getCallingUserId();
-            WallpaperData wallpaper = getWallpaperSafeLocked(userId);
+            if (DEBUG) Slog.v(TAG, "setWallpaper which=0x" + Integer.toHexString(which));
+            WallpaperData wallpaper;
+
+            wallpaper = getWallpaperSafeLocked(userId, which);
             final long ident = Binder.clearCallingIdentity();
             try {
                 ParcelFileDescriptor pfd = updateWallpaperBitmapLocked(name, wallpaper, extras);
                 if (pfd != null) {
                     wallpaper.imageWallpaperPending = true;
+                    wallpaper.whichPending = which;
                     wallpaper.setComplete = completion;
                     wallpaper.cropHint.set(cropHint);
                 }
@@ -1048,10 +1198,9 @@
                         FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
                         -1, -1);
             }
-            File file = new File(dir, WALLPAPER);
-            ParcelFileDescriptor fd = ParcelFileDescriptor.open(file,
+            ParcelFileDescriptor fd = ParcelFileDescriptor.open(wallpaper.wallpaperFile,
                     MODE_CREATE|MODE_READ_WRITE|MODE_TRUNCATE);
-            if (!SELinux.restorecon(file)) {
+            if (!SELinux.restorecon(wallpaper.wallpaperFile)) {
                 return null;
             }
             wallpaper.name = name;
@@ -1061,7 +1210,7 @@
             }
             if (DEBUG) {
                 Slog.v(TAG, "updateWallpaperBitmapLocked() : id=" + wallpaper.wallpaperId
-                        + " name=" + name);
+                        + " name=" + name + " file=" + wallpaper.wallpaperFile.getName());
             }
             return fd;
         } catch (FileNotFoundException e) {
@@ -1417,11 +1566,36 @@
      * want to update the data.  The data is going to be applied when the user switch observer
      * is eventually executed.
      */
-    private WallpaperData getWallpaperSafeLocked(int userId) {
-        WallpaperData wallpaper = mWallpaperMap.get(userId);
+    private WallpaperData getWallpaperSafeLocked(int userId, int which) {
+        // We're setting either just system (work with the system wallpaper),
+        // both (also work with the system wallpaper), or just the lock
+        // wallpaper (update against the existing lock wallpaper if any).
+        // Combined or just-system operations use the 'system' WallpaperData
+        // for this use; lock-only operations use the dedicated one.
+        final SparseArray<WallpaperData> whichSet =
+                (which == FLAG_SET_LOCK) ? mLockWallpaperMap : mWallpaperMap;
+        WallpaperData wallpaper = whichSet.get(userId);
         if (wallpaper == null) {
+            // common case, this is the first lookup post-boot of the system or
+            // unified lock, so we bring up the saved state lazily now and recheck.
             loadSettingsLocked(userId);
-            wallpaper = mWallpaperMap.get(userId);
+            wallpaper = whichSet.get(userId);
+            // if it's still null here, this is a lock-only operation and there is not
+            // yet a lock-only wallpaper set for this user, so we need to establish
+            // it now.
+            if (wallpaper == null) {
+                if (which == FLAG_SET_LOCK) {
+                    wallpaper = new WallpaperData(userId,
+                            WALLPAPER_LOCK_ORIG, WALLPAPER_LOCK_CROP);
+                    mLockWallpaperMap.put(userId, wallpaper);
+                } else {
+                    // sanity fallback: we're in bad shape, but establishing a known
+                    // valid system+lock WallpaperData will keep us from dying.
+                    Slog.wtf(TAG, "Didn't find wallpaper in non-lock case!");
+                    wallpaper = new WallpaperData(userId, WALLPAPER, WALLPAPER_CROP);
+                    mWallpaperMap.put(userId, wallpaper);
+                }
+            }
         }
         return wallpaper;
     }
@@ -1438,8 +1612,9 @@
         }
         WallpaperData wallpaper = mWallpaperMap.get(userId);
         if (wallpaper == null) {
-            wallpaper = new WallpaperData(userId);
+            wallpaper = new WallpaperData(userId, WALLPAPER, WALLPAPER_CROP);
             mWallpaperMap.put(userId, wallpaper);
+            wallpaper.ensureCropExists();
         }
         boolean success = false;
         try {
@@ -1453,28 +1628,10 @@
                 if (type == XmlPullParser.START_TAG) {
                     String tag = parser.getName();
                     if ("wp".equals(tag)) {
-                        final String idString = parser.getAttributeValue(null, "id");
-                        if (idString != null) {
-                            final int id = wallpaper.wallpaperId = Integer.parseInt(idString);
-                            if (id > mWallpaperId) {
-                                mWallpaperId = id;
-                            }
-                        } else {
-                            wallpaper.wallpaperId = makeWallpaperIdLocked();
-                        }
+                        // Common to system + lock wallpapers
+                        parseWallpaperAttributes(parser, wallpaper);
 
-                        wallpaper.width = Integer.parseInt(parser.getAttributeValue(null, "width"));
-                        wallpaper.height = Integer.parseInt(parser
-                                .getAttributeValue(null, "height"));
-                        wallpaper.cropHint.left = getAttributeInt(parser, "cropLeft", 0);
-                        wallpaper.cropHint.top = getAttributeInt(parser, "cropTop", 0);
-                        wallpaper.cropHint.right = getAttributeInt(parser, "cropRight", 0);
-                        wallpaper.cropHint.bottom = getAttributeInt(parser, "cropBottom", 0);
-                        wallpaper.padding.left = getAttributeInt(parser, "paddingLeft", 0);
-                        wallpaper.padding.top = getAttributeInt(parser, "paddingTop", 0);
-                        wallpaper.padding.right = getAttributeInt(parser, "paddingRight", 0);
-                        wallpaper.padding.bottom = getAttributeInt(parser, "paddingBottom", 0);
-                        wallpaper.name = parser.getAttributeValue(null, "name");
+                        // A system wallpaper might also be a live wallpaper
                         String comp = parser.getAttributeValue(null, "component");
                         wallpaper.nextWallpaperComponent = comp != null
                                 ? ComponentName.unflattenFromString(comp)
@@ -1493,6 +1650,15 @@
                             Slog.v(TAG, "mNextWallpaperComponent:"
                                     + wallpaper.nextWallpaperComponent);
                         }
+                    } else if ("kwp".equals(tag)) {
+                        // keyguard-specific wallpaper for this user
+                        WallpaperData lockWallpaper = mLockWallpaperMap.get(userId);
+                        if (lockWallpaper == null) {
+                            lockWallpaper = new WallpaperData(userId,
+                                    WALLPAPER_LOCK_ORIG, WALLPAPER_LOCK_CROP);
+                            mLockWallpaperMap.put(userId, lockWallpaper);
+                        }
+                        parseWallpaperAttributes(parser, lockWallpaper);
                     }
                 }
             } while (type != XmlPullParser.END_DOCUMENT);
@@ -1543,6 +1709,31 @@
         }
     }
 
+    private void parseWallpaperAttributes(XmlPullParser parser, WallpaperData wallpaper) {
+        final String idString = parser.getAttributeValue(null, "id");
+        if (idString != null) {
+            final int id = wallpaper.wallpaperId = Integer.parseInt(idString);
+            if (id > mWallpaperId) {
+                mWallpaperId = id;
+            }
+        } else {
+            wallpaper.wallpaperId = makeWallpaperIdLocked();
+        }
+
+        wallpaper.width = Integer.parseInt(parser.getAttributeValue(null, "width"));
+        wallpaper.height = Integer.parseInt(parser
+                .getAttributeValue(null, "height"));
+        wallpaper.cropHint.left = getAttributeInt(parser, "cropLeft", 0);
+        wallpaper.cropHint.top = getAttributeInt(parser, "cropTop", 0);
+        wallpaper.cropHint.right = getAttributeInt(parser, "cropRight", 0);
+        wallpaper.cropHint.bottom = getAttributeInt(parser, "cropBottom", 0);
+        wallpaper.padding.left = getAttributeInt(parser, "paddingLeft", 0);
+        wallpaper.padding.top = getAttributeInt(parser, "paddingTop", 0);
+        wallpaper.padding.right = getAttributeInt(parser, "paddingRight", 0);
+        wallpaper.padding.bottom = getAttributeInt(parser, "paddingBottom", 0);
+        wallpaper.name = parser.getAttributeValue(null, "name");
+    }
+
     private int getMaximumSizeDimension() {
         WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
         Display d = wm.getDefaultDisplay();