Merge "Revert "New SMS and MMS APIs and semantics (1/4)""
diff --git a/Android.mk b/Android.mk
index b12a987..589279d 100644
--- a/Android.mk
+++ b/Android.mk
@@ -130,6 +130,7 @@
 	core/java/android/content/pm/IPackageInstallObserver.aidl \
 	core/java/android/content/pm/IPackageInstallObserver2.aidl \
 	core/java/android/content/pm/IPackageInstaller.aidl \
+	core/java/android/content/pm/IPackageInstallerObserver.aidl \
 	core/java/android/content/pm/IPackageInstallerSession.aidl \
 	core/java/android/content/pm/IPackageManager.aidl \
 	core/java/android/content/pm/IPackageMoveObserver.aidl \
diff --git a/api/current.txt b/api/current.txt
index e1cc501..f588524 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -14205,11 +14205,14 @@
 
   public abstract class Image implements java.lang.AutoCloseable {
     method public abstract void close();
+    method public android.graphics.Rect getCropRect();
     method public abstract int getFormat();
     method public abstract int getHeight();
     method public abstract android.media.Image.Plane[] getPlanes();
     method public abstract long getTimestamp();
     method public abstract int getWidth();
+    method public void setCropRect(android.graphics.Rect);
+    field protected android.graphics.Rect mCropRect;
   }
 
   public static abstract class Image.Plane {
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index 92e9290..6cca03b 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -27,10 +27,11 @@
 import android.content.pm.IPackageDeleteObserver;
 import android.content.pm.IPackageInstaller;
 import android.content.pm.IPackageManager;
+import android.content.pm.InstallSessionParams;
 import android.content.pm.InstrumentationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInstaller;
-import android.content.pm.PackageInstallerParams;
+import android.content.pm.PackageInstaller.InstallResultCallback;
 import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
@@ -768,6 +769,31 @@
         }
     }
 
+    class LocalInstallResultCallback extends InstallResultCallback {
+        boolean finished;
+        boolean success;
+        String msg;
+
+        private void setResult(boolean success, String msg) {
+            synchronized (this) {
+                this.finished = true;
+                this.success = success;
+                this.msg = msg;
+                notifyAll();
+            }
+        }
+
+        @Override
+        public void onSuccess() {
+            setResult(true, null);
+        }
+
+        @Override
+        public void onFailure(String msg) {
+            setResult(false, msg);
+        }
+    }
+
     /**
      * Converts a failure code into a string by using reflection to find a matching constant
      * in PackageManager.
@@ -989,7 +1015,7 @@
     private void runInstallCreate() throws RemoteException {
         String installerPackageName = null;
 
-        final PackageInstallerParams params = new PackageInstallerParams();
+        final InstallSessionParams params = new InstallSessionParams();
         params.installFlags = PackageManager.INSTALL_ALL_USERS;
         params.fullInstall = true;
 
@@ -1084,21 +1110,21 @@
         try {
             session = new PackageInstaller.Session(mInstaller.openSession(sessionId));
 
-            final LocalPackageInstallObserver observer = new LocalPackageInstallObserver();
-            session.install(observer);
+            final LocalInstallResultCallback callback = new LocalInstallResultCallback();
+            session.install(callback);
 
-            synchronized (observer) {
-                while (!observer.finished) {
+            synchronized (callback) {
+                while (!callback.finished) {
                     try {
-                        observer.wait();
+                        callback.wait();
                     } catch (InterruptedException e) {
                     }
                 }
-                if (observer.result != PackageManager.INSTALL_SUCCEEDED) {
-                    throw new IllegalStateException(
-                            "Failure [" + installFailureToString(observer) + "]");
+                if (!callback.success) {
+                    throw new IllegalStateException("Failure [" + callback.msg + "]");
                 }
             }
+
             System.out.println("Success");
         } finally {
             IoUtils.closeQuietly(session);
diff --git a/core/java/android/app/PackageInstallObserver.java b/core/java/android/app/PackageInstallObserver.java
index 941efbd..7117111 100644
--- a/core/java/android/app/PackageInstallObserver.java
+++ b/core/java/android/app/PackageInstallObserver.java
@@ -23,13 +23,14 @@
 public class PackageInstallObserver {
     private final IPackageInstallObserver2.Stub mBinder = new IPackageInstallObserver2.Stub() {
         @Override
-        public void packageInstalled(String basePackageName, Bundle extras, int returnCode) {
+        public void packageInstalled(String basePackageName, Bundle extras, int returnCode,
+                String msg) {
             PackageInstallObserver.this.packageInstalled(basePackageName, extras, returnCode);
         }
     };
 
     /** {@hide} */
-    public IPackageInstallObserver2.Stub getBinder() {
+    public IPackageInstallObserver2 getBinder() {
         return mBinder;
     }
 
@@ -50,4 +51,9 @@
      */
     public void packageInstalled(String basePackageName, Bundle extras, int returnCode) {
     }
+
+    public void packageInstalled(String basePackageName, Bundle extras, int returnCode,
+            String msg) {
+        packageInstalled(basePackageName, extras, returnCode);
+    }
 }
diff --git a/core/java/android/app/PackageUninstallObserver.java b/core/java/android/app/PackageUninstallObserver.java
index 0a960a7..83fc380 100644
--- a/core/java/android/app/PackageUninstallObserver.java
+++ b/core/java/android/app/PackageUninstallObserver.java
@@ -28,7 +28,7 @@
     };
 
     /** {@hide} */
-    public IPackageDeleteObserver.Stub getBinder() {
+    public IPackageDeleteObserver getBinder() {
         return mBinder;
     }
 
diff --git a/core/java/android/content/pm/IPackageInstallObserver2.aidl b/core/java/android/content/pm/IPackageInstallObserver2.aidl
index 7205ce7..824d730 100644
--- a/core/java/android/content/pm/IPackageInstallObserver2.aidl
+++ b/core/java/android/content/pm/IPackageInstallObserver2.aidl
@@ -40,5 +40,5 @@
      * </tr>
      * </table>
      */
-    void packageInstalled(String basePackageName, in Bundle extras, int returnCode);
+    void packageInstalled(String basePackageName, in Bundle extras, int returnCode, String msg);
 }
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index 4d6ee64..32460c9 100644
--- a/core/java/android/content/pm/IPackageInstaller.aidl
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -17,17 +17,22 @@
 package android.content.pm;
 
 import android.content.pm.IPackageDeleteObserver;
+import android.content.pm.IPackageInstallerObserver;
 import android.content.pm.IPackageInstallerSession;
-import android.content.pm.PackageInstallerParams;
+import android.content.pm.InstallSessionInfo;
+import android.content.pm.InstallSessionParams;
 import android.os.ParcelFileDescriptor;
 
 /** {@hide} */
 interface IPackageInstaller {
-    int createSession(String installerPackageName, in PackageInstallerParams params, int userId);
+    int createSession(String installerPackageName, in InstallSessionParams params, int userId);
     IPackageInstallerSession openSession(int sessionId);
 
-    int[] getSessions(String installerPackageName, int userId);
+    List<InstallSessionInfo> getSessions(int userId);
 
-    void uninstall(String basePackageName, int flags, in IPackageDeleteObserver observer, int userId);
-    void uninstallSplit(String basePackageName, String splitName, int flags, in IPackageDeleteObserver observer, int userId);
+    void registerObserver(IPackageInstallerObserver observer, int userId);
+    void unregisterObserver(IPackageInstallerObserver observer, int userId);
+
+    void uninstall(String packageName, int flags, in IPackageDeleteObserver observer, int userId);
+    void uninstallSplit(String packageName, String splitName, int flags, in IPackageDeleteObserver observer, int userId);
 }
diff --git a/core/java/android/content/pm/IPackageInstallerObserver.aidl b/core/java/android/content/pm/IPackageInstallerObserver.aidl
new file mode 100644
index 0000000..85660e4
--- /dev/null
+++ b/core/java/android/content/pm/IPackageInstallerObserver.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2014 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.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import android.content.pm.InstallSessionInfo;
+
+/** {@hide} */
+oneway interface IPackageInstallerObserver {
+    void onSessionCreated(in InstallSessionInfo info);
+    void onSessionProgress(int sessionId, int progress);
+    void onSessionFinished(int sessionId, boolean success);
+}
diff --git a/core/java/android/content/pm/InstallSessionInfo.aidl b/core/java/android/content/pm/InstallSessionInfo.aidl
new file mode 100644
index 0000000..3d21bbd
--- /dev/null
+++ b/core/java/android/content/pm/InstallSessionInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2014 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.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+parcelable InstallSessionInfo;
diff --git a/core/java/android/content/pm/InstallSessionInfo.java b/core/java/android/content/pm/InstallSessionInfo.java
new file mode 100644
index 0000000..45606d1
--- /dev/null
+++ b/core/java/android/content/pm/InstallSessionInfo.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2014 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.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import android.graphics.Bitmap;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** {@hide} */
+public class InstallSessionInfo implements Parcelable {
+    public int sessionId;
+    public String installerPackageName;
+    public int progress;
+
+    public boolean fullInstall;
+    public String packageName;
+    public Bitmap icon;
+    public CharSequence title;
+
+    /** {@hide} */
+    public InstallSessionInfo() {
+    }
+
+    /** {@hide} */
+    public InstallSessionInfo(Parcel source) {
+        sessionId = source.readInt();
+        installerPackageName = source.readString();
+        progress = source.readInt();
+
+        fullInstall = source.readInt() != 0;
+        packageName = source.readString();
+        icon = source.readParcelable(null);
+        title = source.readString();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(sessionId);
+        dest.writeString(installerPackageName);
+        dest.writeInt(progress);
+
+        dest.writeInt(fullInstall ? 1 : 0);
+        dest.writeString(packageName);
+        dest.writeParcelable(icon, flags);
+        dest.writeString(title != null ? title.toString() : null);
+    }
+
+    public static final Parcelable.Creator<InstallSessionInfo>
+            CREATOR = new Parcelable.Creator<InstallSessionInfo>() {
+                @Override
+                public InstallSessionInfo createFromParcel(Parcel p) {
+                    return new InstallSessionInfo(p);
+                }
+
+                @Override
+                public InstallSessionInfo[] newArray(int size) {
+                    return new InstallSessionInfo[size];
+                }
+            };
+}
diff --git a/core/java/android/content/pm/InstallSessionParams.aidl b/core/java/android/content/pm/InstallSessionParams.aidl
new file mode 100644
index 0000000..81b7574
--- /dev/null
+++ b/core/java/android/content/pm/InstallSessionParams.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2014 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.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+parcelable InstallSessionParams;
diff --git a/core/java/android/content/pm/PackageInstallerParams.java b/core/java/android/content/pm/InstallSessionParams.java
similarity index 72%
rename from core/java/android/content/pm/PackageInstallerParams.java
rename to core/java/android/content/pm/InstallSessionParams.java
index 527edee..f683523 100644
--- a/core/java/android/content/pm/PackageInstallerParams.java
+++ b/core/java/android/content/pm/InstallSessionParams.java
@@ -21,12 +21,8 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
-/**
- * Parameters that define an installation session.
- *
- * {@hide}
- */
-public class PackageInstallerParams implements Parcelable {
+/** {@hide} */
+public class InstallSessionParams implements Parcelable {
 
     // TODO: extend to support remaining VerificationParams
 
@@ -41,11 +37,11 @@
     /** {@hide} */
     public long deltaSize = -1;
     /** {@hide} */
-    public String basePackageName;
+    public String packageName;
     /** {@hide} */
     public Bitmap icon;
     /** {@hide} */
-    public String title;
+    public CharSequence title;
     /** {@hide} */
     public Uri originatingUri;
     /** {@hide} */
@@ -53,20 +49,18 @@
     /** {@hide} */
     public String abiOverride;
 
-    public PackageInstallerParams() {
+    public InstallSessionParams() {
     }
 
     /** {@hide} */
-    public PackageInstallerParams(Parcel source) {
+    public InstallSessionParams(Parcel source) {
         fullInstall = source.readInt() != 0;
         installFlags = source.readInt();
         installLocation = source.readInt();
         signatures = (Signature[]) source.readParcelableArray(null);
         deltaSize = source.readLong();
-        basePackageName = source.readString();
-        if (source.readInt() != 0) {
-            icon = Bitmap.CREATOR.createFromParcel(source);
-        }
+        packageName = source.readString();
+        icon = source.readParcelable(null);
         title = source.readString();
         originatingUri = source.readParcelable(null);
         referrerUri = source.readParcelable(null);
@@ -93,8 +87,8 @@
         this.deltaSize = deltaSize;
     }
 
-    public void setBasePackageName(String basePackageName) {
-        this.basePackageName = basePackageName;
+    public void setPackageName(String packageName) {
+        this.packageName = packageName;
     }
 
     public void setIcon(Bitmap icon) {
@@ -102,7 +96,7 @@
     }
 
     public void setTitle(CharSequence title) {
-        this.title = (title != null) ? title.toString() : null;
+        this.title = title;
     }
 
     public void setOriginatingUri(Uri originatingUri) {
@@ -125,29 +119,24 @@
         dest.writeInt(installLocation);
         dest.writeParcelableArray(signatures, flags);
         dest.writeLong(deltaSize);
-        dest.writeString(basePackageName);
-        if (icon != null) {
-            dest.writeInt(1);
-            icon.writeToParcel(dest, flags);
-        } else {
-            dest.writeInt(0);
-        }
-        dest.writeString(title);
+        dest.writeString(packageName);
+        dest.writeParcelable(icon, flags);
+        dest.writeString(title != null ? title.toString() : null);
         dest.writeParcelable(originatingUri, flags);
         dest.writeParcelable(referrerUri, flags);
         dest.writeString(abiOverride);
     }
 
-    public static final Parcelable.Creator<PackageInstallerParams>
-            CREATOR = new Parcelable.Creator<PackageInstallerParams>() {
+    public static final Parcelable.Creator<InstallSessionParams>
+            CREATOR = new Parcelable.Creator<InstallSessionParams>() {
                 @Override
-                public PackageInstallerParams createFromParcel(Parcel p) {
-                    return new PackageInstallerParams(p);
+                public InstallSessionParams createFromParcel(Parcel p) {
+                    return new InstallSessionParams(p);
                 }
 
                 @Override
-                public PackageInstallerParams[] newArray(int size) {
-                    return new PackageInstallerParams[size];
+                public InstallSessionParams[] newArray(int size) {
+                    return new InstallSessionParams[size];
                 }
             };
 }
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index a60a2fe..9d756f7 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -19,12 +19,14 @@
 import android.app.PackageInstallObserver;
 import android.app.PackageUninstallObserver;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Bundle;
 import android.os.FileBridge;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 
 import java.io.Closeable;
 import java.io.OutputStream;
+import java.util.List;
 
 /** {@hide} */
 public class PackageInstaller {
@@ -42,9 +44,9 @@
         mUserId = userId;
     }
 
-    public boolean isPackageAvailable(String basePackageName) {
+    public boolean isPackageAvailable(String packageName) {
         try {
-            final ApplicationInfo info = mPm.getApplicationInfo(basePackageName,
+            final ApplicationInfo info = mPm.getApplicationInfo(packageName,
                     PackageManager.GET_UNINSTALLED_PACKAGES);
             return ((info.flags & ApplicationInfo.FLAG_INSTALLED) != 0);
         } catch (NameNotFoundException e) {
@@ -52,17 +54,17 @@
         }
     }
 
-    public void installAvailablePackage(String basePackageName, PackageInstallObserver observer) {
+    public void installAvailablePackage(String packageName, PackageInstallObserver observer) {
         int returnCode;
         try {
-            returnCode = mPm.installExistingPackage(basePackageName);
+            returnCode = mPm.installExistingPackage(packageName);
         } catch (NameNotFoundException e) {
             returnCode = PackageManager.INSTALL_FAILED_PACKAGE_CHANGED;
         }
-        observer.packageInstalled(basePackageName, null, returnCode);
+        observer.packageInstalled(packageName, null, returnCode);
     }
 
-    public int createSession(PackageInstallerParams params) {
+    public int createSession(InstallSessionParams params) {
         try {
             return mInstaller.createSession(mInstallerPackageName, params, mUserId);
         } catch (RemoteException e) {
@@ -78,26 +80,74 @@
         }
     }
 
-    public int[] getSessions() {
+    public List<InstallSessionInfo> getSessions() {
+        // TODO: filter based on caller
+        // TODO: let launcher app see all active sessions
         try {
-            return mInstaller.getSessions(mInstallerPackageName, mUserId);
+            return mInstaller.getSessions(mUserId);
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         }
     }
 
-    public void uninstall(String basePackageName, PackageUninstallObserver observer) {
+    public void uninstall(String packageName, UninstallResultCallback callback) {
         try {
-            mInstaller.uninstall(basePackageName, 0, observer.getBinder(), mUserId);
+            mInstaller.uninstall(packageName, 0,
+                    new UninstallResultCallbackDelegate(callback).getBinder(), mUserId);
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         }
     }
 
-    public void uninstall(String basePackageName, String splitName,
-            PackageUninstallObserver observer) {
+    public void uninstall(String packageName, String splitName, UninstallResultCallback callback) {
         try {
-            mInstaller.uninstallSplit(basePackageName, splitName, 0, observer.getBinder(), mUserId);
+            mInstaller.uninstallSplit(packageName, splitName, 0,
+                    new UninstallResultCallbackDelegate(callback).getBinder(), mUserId);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    public static abstract class SessionObserver {
+        private final IPackageInstallerObserver.Stub mBinder = new IPackageInstallerObserver.Stub() {
+            @Override
+            public void onSessionCreated(InstallSessionInfo info) {
+                SessionObserver.this.onCreated(info);
+            }
+
+            @Override
+            public void onSessionProgress(int sessionId, int progress) {
+                SessionObserver.this.onProgress(sessionId, progress);
+            }
+
+            @Override
+            public void onSessionFinished(int sessionId, boolean success) {
+                SessionObserver.this.onFinished(sessionId, success);
+            }
+        };
+
+        /** {@hide} */
+        public IPackageInstallerObserver getBinder() {
+            return mBinder;
+        }
+
+        public abstract void onCreated(InstallSessionInfo info);
+        public abstract void onProgress(int sessionId, int progress);
+        public abstract void onFinished(int sessionId, boolean success);
+    }
+
+    public void registerObserver(SessionObserver observer) {
+        // TODO: consider restricting to current launcher app
+        try {
+            mInstaller.registerObserver(observer.getBinder(), mUserId);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    public void unregisterObserver(SessionObserver observer) {
+        try {
+            mInstaller.unregisterObserver(observer.getBinder(), mUserId);
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         }
@@ -147,9 +197,9 @@
             }
         }
 
-        public void install(PackageInstallObserver observer) {
+        public void install(InstallResultCallback callback) {
             try {
-                mSession.install(observer.getBinder());
+                mSession.install(new InstallResultCallbackDelegate(callback).getBinder());
             } catch (RemoteException e) {
                 throw e.rethrowAsRuntimeException();
             }
@@ -168,4 +218,143 @@
             }
         }
     }
+
+    public static abstract class UninstallResultCallback {
+        public abstract void onSuccess();
+        public abstract void onFailure(String msg);
+    }
+
+    private static class UninstallResultCallbackDelegate extends PackageUninstallObserver {
+        private final UninstallResultCallback target;
+
+        public UninstallResultCallbackDelegate(UninstallResultCallback target) {
+            this.target = target;
+        }
+
+        @Override
+        public void onUninstallFinished(String basePackageName, int returnCode) {
+            final String msg = null;
+
+            switch (returnCode) {
+                case PackageManager.DELETE_SUCCEEDED: target.onSuccess(); break;
+                case PackageManager.DELETE_FAILED_INTERNAL_ERROR: target.onFailure("DELETE_FAILED_INTERNAL_ERROR: " + msg); break;
+                case PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER: target.onFailure("DELETE_FAILED_DEVICE_POLICY_MANAGER: " + msg); break;
+                case PackageManager.DELETE_FAILED_USER_RESTRICTED: target.onFailure("DELETE_FAILED_USER_RESTRICTED: " + msg); break;
+                case PackageManager.DELETE_FAILED_OWNER_BLOCKED: target.onFailure("DELETE_FAILED_OWNER_BLOCKED: " + msg); break;
+                default: target.onFailure(msg); break;
+            }
+        }
+    }
+
+    public static abstract class InstallResultCallback {
+        /**
+         * The session installed successfully.
+         */
+        public abstract void onSuccess();
+
+        /**
+         * General unclassified failure. You may be interested in overriding
+         * more granular classifications.
+         */
+        public abstract void onFailure(String msg);
+
+        /**
+         * One or more of the APKs included in the session was invalid. For
+         * example, they might be malformed, corrupt, incorrectly signed,
+         * mismatched, etc. The installer may want to try downloading and
+         * installing again.
+         */
+        public void onFailureInvalid(String msg) {
+            onFailure(msg);
+        }
+
+        /**
+         * This install session conflicts (or is inconsistent with) with
+         * another package already installed on the device. For example, an
+         * existing permission, incompatible certificates, etc. The user may
+         * be able to uninstall another app to fix the issue.
+         */
+        public void onFailureConflict(String msg, String packageName) {
+            onFailure(msg);
+        }
+
+        /**
+         * This install session failed due to storage issues. For example,
+         * the device may be running low on space, or the required external
+         * media may be unavailable. The user may be able to help free space
+         * or insert the correct media.
+         */
+        public void onFailureStorage(String msg) {
+            onFailure(msg);
+        }
+
+        /**
+         * This install session is fundamentally incompatible with this
+         * device. For example, the package may require a hardware feature
+         * that doesn't exist, it may be missing native code for the device
+         * ABI, or it requires a newer SDK version, etc. This install would
+         * never succeed.
+         */
+        public void onFailureIncompatible(String msg) {
+            onFailure(msg);
+        }
+    }
+
+    private static class InstallResultCallbackDelegate extends PackageInstallObserver {
+        private final InstallResultCallback target;
+
+        public InstallResultCallbackDelegate(InstallResultCallback target) {
+            this.target = target;
+        }
+
+        @Override
+        public void packageInstalled(String basePackageName, Bundle extras, int returnCode,
+                String msg) {
+            final String otherPackage = null;
+
+            switch (returnCode) {
+                case PackageManager.INSTALL_SUCCEEDED: target.onSuccess(); break;
+                case PackageManager.INSTALL_FAILED_ALREADY_EXISTS: target.onFailureConflict("INSTALL_FAILED_ALREADY_EXISTS: " + msg, otherPackage); break;
+                case PackageManager.INSTALL_FAILED_INVALID_APK: target.onFailureInvalid("INSTALL_FAILED_INVALID_APK: " + msg); break;
+                case PackageManager.INSTALL_FAILED_INVALID_URI: target.onFailureInvalid("INSTALL_FAILED_INVALID_URI: " + msg); break;
+                case PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE: target.onFailureStorage("INSTALL_FAILED_INSUFFICIENT_STORAGE: " + msg); break;
+                case PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE: target.onFailureConflict("INSTALL_FAILED_DUPLICATE_PACKAGE: " + msg, otherPackage); break;
+                case PackageManager.INSTALL_FAILED_NO_SHARED_USER: target.onFailureConflict("INSTALL_FAILED_NO_SHARED_USER: " + msg, otherPackage); break;
+                case PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE: target.onFailureConflict("INSTALL_FAILED_UPDATE_INCOMPATIBLE: " + msg, otherPackage); break;
+                case PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE: target.onFailureConflict("INSTALL_FAILED_SHARED_USER_INCOMPATIBLE: " + msg, otherPackage); break;
+                case PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY: target.onFailureIncompatible("INSTALL_FAILED_MISSING_SHARED_LIBRARY: " + msg); break;
+                case PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE: target.onFailureConflict("INSTALL_FAILED_REPLACE_COULDNT_DELETE: " + msg, otherPackage); break;
+                case PackageManager.INSTALL_FAILED_DEXOPT: target.onFailureInvalid("INSTALL_FAILED_DEXOPT: " + msg); break;
+                case PackageManager.INSTALL_FAILED_OLDER_SDK: target.onFailureIncompatible("INSTALL_FAILED_OLDER_SDK: " + msg); break;
+                case PackageManager.INSTALL_FAILED_CONFLICTING_PROVIDER: target.onFailureConflict("INSTALL_FAILED_CONFLICTING_PROVIDER: " + msg, otherPackage); break;
+                case PackageManager.INSTALL_FAILED_NEWER_SDK: target.onFailureIncompatible("INSTALL_FAILED_NEWER_SDK: " + msg); break;
+                case PackageManager.INSTALL_FAILED_TEST_ONLY: target.onFailureInvalid("INSTALL_FAILED_TEST_ONLY: " + msg); break;
+                case PackageManager.INSTALL_FAILED_CPU_ABI_INCOMPATIBLE: target.onFailureIncompatible("INSTALL_FAILED_CPU_ABI_INCOMPATIBLE: " + msg); break;
+                case PackageManager.INSTALL_FAILED_MISSING_FEATURE: target.onFailureIncompatible("INSTALL_FAILED_MISSING_FEATURE: " + msg); break;
+                case PackageManager.INSTALL_FAILED_CONTAINER_ERROR: target.onFailureStorage("INSTALL_FAILED_CONTAINER_ERROR: " + msg); break;
+                case PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION: target.onFailureStorage("INSTALL_FAILED_INVALID_INSTALL_LOCATION: " + msg); break;
+                case PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE: target.onFailureStorage("INSTALL_FAILED_MEDIA_UNAVAILABLE: " + msg); break;
+                case PackageManager.INSTALL_FAILED_VERIFICATION_TIMEOUT: target.onFailure("INSTALL_FAILED_VERIFICATION_TIMEOUT: " + msg); break;
+                case PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE: target.onFailure("INSTALL_FAILED_VERIFICATION_FAILURE: " + msg); break;
+                case PackageManager.INSTALL_FAILED_PACKAGE_CHANGED: target.onFailureInvalid("INSTALL_FAILED_PACKAGE_CHANGED: " + msg); break;
+                case PackageManager.INSTALL_FAILED_UID_CHANGED: target.onFailureInvalid("INSTALL_FAILED_UID_CHANGED: " + msg); break;
+                case PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE: target.onFailureInvalid("INSTALL_FAILED_VERSION_DOWNGRADE: " + msg); break;
+                case PackageManager.INSTALL_PARSE_FAILED_NOT_APK: target.onFailureInvalid("INSTALL_PARSE_FAILED_NOT_APK: " + msg); break;
+                case PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST: target.onFailureInvalid("INSTALL_PARSE_FAILED_BAD_MANIFEST: " + msg); break;
+                case PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION: target.onFailureInvalid("INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION: " + msg); break;
+                case PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES: target.onFailureInvalid("INSTALL_PARSE_FAILED_NO_CERTIFICATES: " + msg); break;
+                case PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES: target.onFailureInvalid("INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES: " + msg); break;
+                case PackageManager.INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING: target.onFailureInvalid("INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING: " + msg); break;
+                case PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME: target.onFailureInvalid("INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME: " + msg); break;
+                case PackageManager.INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID: target.onFailureInvalid("INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID: " + msg); break;
+                case PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED: target.onFailureInvalid("INSTALL_PARSE_FAILED_MANIFEST_MALFORMED: " + msg); break;
+                case PackageManager.INSTALL_PARSE_FAILED_MANIFEST_EMPTY: target.onFailureInvalid("INSTALL_PARSE_FAILED_MANIFEST_EMPTY: " + msg); break;
+                case PackageManager.INSTALL_FAILED_INTERNAL_ERROR: target.onFailure("INSTALL_FAILED_INTERNAL_ERROR: " + msg); break;
+                case PackageManager.INSTALL_FAILED_USER_RESTRICTED: target.onFailureIncompatible("INSTALL_FAILED_USER_RESTRICTED: " + msg); break;
+                case PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION: target.onFailureConflict("INSTALL_FAILED_DUPLICATE_PERMISSION: " + msg, otherPackage); break;
+                case PackageManager.INSTALL_FAILED_NO_MATCHING_ABIS: target.onFailureInvalid("INSTALL_FAILED_NO_MATCHING_ABIS: " + msg); break;
+                default: target.onFailure(msg); break;
+            }
+        }
+    }
 }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index a0e56f6..7e783eb 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -17,9 +17,9 @@
 package android.content.pm;
 
 import android.annotation.IntDef;
-import android.annotation.SystemApi;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
 import android.app.PackageInstallObserver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -29,13 +29,12 @@
 import android.content.pm.PackageParser.PackageParserException;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
-import android.graphics.Bitmap;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.Environment;
 import android.os.UserHandle;
 import android.util.AndroidException;
-import android.util.DisplayMetrics;
 
 import java.io.File;
 import java.lang.annotation.Retention;
@@ -750,7 +749,7 @@
      * permission that is already defined by some existing package.
      *
      * <p>The package name of the app which has already defined the permission is passed to
-     * a {@link IPackageInstallObserver2}, if any, as the {@link #EXTRA_EXISTING_PACKAGE}
+     * a {@link PackageInstallObserver}, if any, as the {@link #EXTRA_EXISTING_PACKAGE}
      * string extra; and the name of the permission being redefined is passed in the
      * {@link #EXTRA_EXISTING_PERMISSION} string extra.
      * @hide
@@ -1544,7 +1543,7 @@
             = "android.content.pm.extra.PERMISSION_LIST";
 
     /**
-     * String extra for {@link IPackageInstallObserver2} in the 'extras' Bundle in case of
+     * String extra for {@link PackageInstallObserver} in the 'extras' Bundle in case of
      * {@link #INSTALL_FAILED_DUPLICATE_PERMISSION}.  This extra names the package which provides
      * the existing definition for the permission.
      * @hide
@@ -1553,7 +1552,7 @@
             = "android.content.pm.extra.FAILURE_EXISTING_PACKAGE";
 
     /**
-     * String extra for {@link IPackageInstallObserver2} in the 'extras' Bundle in case of
+     * String extra for {@link PackageInstallObserver} in the 'extras' Bundle in case of
      * {@link #INSTALL_FAILED_DUPLICATE_PERMISSION}.  This extra names the permission that is
      * being redundantly defined by the package being installed.
      * @hide
@@ -2941,26 +2940,29 @@
     }
 
     /**
-     * @hide
-     *
-     * Install a package. Since this may take a little while, the result will
-     * be posted back to the given observer.  An installation will fail if the calling context
-     * lacks the {@link android.Manifest.permission#INSTALL_PACKAGES} permission, if the
-     * package named in the package file's manifest is already installed, or if there's no space
-     * available on the device.
-     *
-     * @param packageURI The location of the package file to install.  This can be a 'file:' or a
-     * 'content:' URI.
-     * @param observer An observer callback to get notified when the package installation is
-     * complete. {@link IPackageInstallObserver#packageInstalled(String, int)} will be
-     * called when that happens.  This parameter must not be null.
+     * @hide Install a package. Since this may take a little while, the result
+     *       will be posted back to the given observer. An installation will
+     *       fail if the calling context lacks the
+     *       {@link android.Manifest.permission#INSTALL_PACKAGES} permission, if
+     *       the package named in the package file's manifest is already
+     *       installed, or if there's no space available on the device.
+     * @param packageURI The location of the package file to install. This can
+     *            be a 'file:' or a 'content:' URI.
+     * @param observer An observer callback to get notified when the package
+     *            installation is complete.
+     *            {@link IPackageInstallObserver#packageInstalled(String, int)}
+     *            will be called when that happens. This parameter must not be
+     *            null.
      * @param flags - possible values: {@link #INSTALL_FORWARD_LOCK},
-     * {@link #INSTALL_REPLACE_EXISTING}, {@link #INSTALL_ALLOW_TEST}.
-     * @param installerPackageName Optional package name of the application that is performing the
-     * installation. This identifies which market the package came from.
-     * @deprecated Use {@link #installPackage(Uri, IPackageInstallObserver2, int, String)}
-     * instead.  This method will continue to be supported but the older observer interface
-     * will not get additional failure details.
+     *            {@link #INSTALL_REPLACE_EXISTING},
+     *            {@link #INSTALL_ALLOW_TEST}.
+     * @param installerPackageName Optional package name of the application that
+     *            is performing the installation. This identifies which market
+     *            the package came from.
+     * @deprecated Use {@link #installPackage(Uri, PackageInstallObserver, int,
+     *             String)} instead. This method will continue to be supported
+     *             but the older observer interface will not get additional
+     *             failure details.
      */
     // @SystemApi
     public abstract void installPackage(
@@ -2977,9 +2979,11 @@
      * @param observer An observer callback to get notified when the package
      *            installation is complete.
      *            {@link IPackageInstallObserver#packageInstalled(String, int)}
-     *            will be called when that happens. This parameter must not be null.
+     *            will be called when that happens. This parameter must not be
+     *            null.
      * @param flags - possible values: {@link #INSTALL_FORWARD_LOCK},
-     *            {@link #INSTALL_REPLACE_EXISTING}, {@link #INSTALL_ALLOW_TEST}.
+     *            {@link #INSTALL_REPLACE_EXISTING},
+     *            {@link #INSTALL_ALLOW_TEST}.
      * @param installerPackageName Optional package name of the application that
      *            is performing the installation. This identifies which market
      *            the package came from.
@@ -2992,10 +2996,11 @@
      *            these parameters describing the encryption and authentication
      *            used. May be {@code null}.
      * @hide
-     * @deprecated Use {@link #installPackageWithVerification(Uri, IPackageInstallObserver2,
-     * int, String, Uri, ManifestDigest, ContainerEncryptionParams)} instead.  This method will
-     * continue to be supported but the older observer interface will not get additional failure
-     * details.
+     * @deprecated Use {@link #installPackageWithVerification(Uri,
+     *             PackageInstallObserver, int, String, Uri, ManifestDigest,
+     *             ContainerEncryptionParams)} instead. This method will
+     *             continue to be supported but the older observer interface
+     *             will not get additional failure details.
      */
     // @SystemApi
     public abstract void installPackageWithVerification(Uri packageURI,
@@ -3013,9 +3018,11 @@
      * @param observer An observer callback to get notified when the package
      *            installation is complete.
      *            {@link IPackageInstallObserver#packageInstalled(String, int)}
-     *            will be called when that happens. This parameter must not be null.
+     *            will be called when that happens. This parameter must not be
+     *            null.
      * @param flags - possible values: {@link #INSTALL_FORWARD_LOCK},
-     *            {@link #INSTALL_REPLACE_EXISTING}, {@link #INSTALL_ALLOW_TEST}.
+     *            {@link #INSTALL_REPLACE_EXISTING},
+     *            {@link #INSTALL_ALLOW_TEST}.
      * @param installerPackageName Optional package name of the application that
      *            is performing the installation. This identifies which market
      *            the package came from.
@@ -3024,12 +3031,12 @@
      * @param encryptionParams if the package to be installed is encrypted,
      *            these parameters describing the encryption and authentication
      *            used. May be {@code null}.
-     *
      * @hide
      * @deprecated Use {@link #installPackageWithVerificationAndEncryption(Uri,
-     * IPackageInstallObserver2, int, String, VerificationParams,
-     * ContainerEncryptionParams)} instead.  This method will continue to be
-     * supported but the older observer interface will not get additional failure details.
+     *             PackageInstallObserver, int, String, VerificationParams,
+     *             ContainerEncryptionParams)} instead. This method will
+     *             continue to be supported but the older observer interface
+     *             will not get additional failure details.
      */
     @Deprecated
     public abstract void installPackageWithVerificationAndEncryption(Uri packageURI,
diff --git a/core/java/com/android/internal/app/ToolbarActionBar.java b/core/java/com/android/internal/app/ToolbarActionBar.java
index 7af52f3..50effd0 100644
--- a/core/java/com/android/internal/app/ToolbarActionBar.java
+++ b/core/java/com/android/internal/app/ToolbarActionBar.java
@@ -65,6 +65,7 @@
         mToolbar = toolbar;
         mDecorToolbar = new ToolbarWidgetWrapper(toolbar);
         mWindowCallback = windowCallback;
+        mDecorToolbar.setWindowCallback(mWindowCallback);
         toolbar.setOnMenuItemClickListener(mMenuClicker);
         mDecorToolbar.setWindowTitle(title);
     }
diff --git a/media/java/android/media/Image.java b/media/java/android/media/Image.java
index a346e17..2856edb 100644
--- a/media/java/android/media/Image.java
+++ b/media/java/android/media/Image.java
@@ -19,6 +19,8 @@
 import java.nio.ByteBuffer;
 import java.lang.AutoCloseable;
 
+import android.graphics.Rect;
+
 /**
  * <p>A single complete image buffer to use with a media source such as a
  * {@link MediaCodec} or a
@@ -121,6 +123,34 @@
      */
     public abstract long getTimestamp();
 
+    protected Rect mCropRect;
+
+    /**
+     * Get the crop rectangle associated with this frame.
+     * <p>
+     * The crop rectangle specifies the region of valid pixels in the image,
+     * using coordinates in the largest-resolution plane.
+     */
+    public Rect getCropRect() {
+        if (mCropRect == null) {
+            return new Rect(0, 0, getWidth(), getHeight());
+        } else {
+            return new Rect(mCropRect); // return a copy
+        }
+    }
+
+    /**
+     * Set the crop rectangle associated with this frame.
+     * <p>
+     * The crop rectangle specifies the region of valid pixels in the image,
+     * using coordinates in the largest-resolution plane.
+     */
+    public void setCropRect(Rect cropRect) {
+        cropRect = new Rect(cropRect);  // make a copy
+        cropRect.intersect(0, 0, getWidth(), getHeight());
+        mCropRect = cropRect;
+    }
+
     /**
      * Get the array of pixel planes for this Image. The number of planes is
      * determined by the format of the Image.
diff --git a/packages/SystemUI/res/drawable/ic_lock_to_app_24dp.xml b/packages/SystemUI/res/drawable/ic_lock_to_app_24dp.xml
new file mode 100644
index 0000000..e5737ee
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_lock_to_app_24dp.xml
@@ -0,0 +1,28 @@
+<!--
+Copyright (C) 2014 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.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+    <size
+        android:width="24.0dp"
+        android:height="24.0dp"/>
+
+    <viewport
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0"/>
+
+    <path
+        android:fill="@color/recents_task_view_lock_to_app_button_color"
+        android:pathData="M18.0,8.0l-1.0,0.0L17.0,6.0c0.0,-2.8 -2.2,-5.0 -5.0,-5.0C9.2,1.0 7.0,3.2 7.0,6.0l0.0,2.0L6.0,8.0c-1.1,0.0 -2.0,0.9 -2.0,2.0l0.0,10.0c0.0,1.1 0.9,2.0 2.0,2.0l12.0,0.0c1.1,0.0 2.0,-0.9 2.0,-2.0L20.0,10.0C20.0,8.9 19.1,8.0 18.0,8.0zM12.0,17.0c-1.1,0.0 -2.0,-0.9 -2.0,-2.0s0.9,-2.0 2.0,-2.0c1.1,0.0 2.0,0.9 2.0,2.0S13.1,17.0 12.0,17.0zM15.1,8.0L8.9,8.0L8.9,6.0c0.0,-1.7 1.4,-3.1 3.1,-3.1c1.7,0.0 3.1,1.4 3.1,3.1L15.1,8.0z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/recents_lock_to_task_button_bg.xml b/packages/SystemUI/res/drawable/recents_lock_to_task_button_bg.xml
new file mode 100644
index 0000000..d38c8a4
--- /dev/null
+++ b/packages/SystemUI/res/drawable/recents_lock_to_task_button_bg.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+     android:color="#ffdadada">
+    <item android:drawable="@color/recents_task_view_lock_to_app_button_background_color" />
+</ripple>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/recents_task_view.xml b/packages/SystemUI/res/layout/recents_task_view.xml
index 2b50a95..7e8bfd32 100644
--- a/packages/SystemUI/res/layout/recents_task_view.xml
+++ b/packages/SystemUI/res/layout/recents_task_view.xml
@@ -62,6 +62,27 @@
             android:visibility="invisible"
             android:src="@drawable/recents_dismiss_light" />
     </com.android.systemui.recents.views.TaskBarView>
+    <FrameLayout
+        android:id="@+id/lock_to_app"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/recents_task_view_lock_to_app_button_height"
+        android:layout_gravity="center_horizontal|bottom"
+        android:background="@drawable/recents_lock_to_task_button_bg"
+        android:visibility="invisible">
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_gravity="center_horizontal"
+            android:gravity="center"
+            android:drawableLeft="@drawable/ic_lock_to_app_24dp"
+            android:drawablePadding="8dp"
+            android:textSize="16sp"
+            android:textColor="@color/recents_task_view_lock_to_app_button_color"
+            android:text="@string/recents_lock_to_app_button_label"
+            android:fontFamily="sans-serif-medium"
+            android:singleLine="true"
+            android:textAllCaps="true" />
+    </FrameLayout>
 </com.android.systemui.recents.views.TaskView>
 
 
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index ef8302d..4cc0bb5 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -53,13 +53,17 @@
     <!-- The recents task bar light text color to be drawn on top of dark backgrounds. -->
     <color name="recents_task_bar_light_text_color">#ffeeeeee</color>
     <!-- The recents task bar dark text color to be drawn on top of light backgrounds. -->
-    <color name="recents_task_bar_dark_text_color">#ff333333</color>
+    <color name="recents_task_bar_dark_text_color">#cc000000</color>
     <!-- The recents task bar light dismiss icon color to be drawn on top of dark backgrounds. -->
     <color name="recents_task_bar_light_dismiss_color">#ffeeeeee</color>
     <!-- The recents task bar dark dismiss icon color to be drawn on top of light backgrounds. -->
-    <color name="recents_task_bar_dark_dismiss_color">#ff333333</color>
+    <color name="recents_task_bar_dark_dismiss_color">#cc000000</color>
     <!-- The recents task bar highlight color. -->
     <color name="recents_task_bar_highlight_color">#28ffffff</color>
+    <!-- The lock to task button background color. -->
+    <color name="recents_task_view_lock_to_app_button_background_color">#ffe6e6e6</color>
+    <!-- The lock to task button foreground color. -->
+    <color name="recents_task_view_lock_to_app_button_color">#ff666666</color>
 
     <color name="keyguard_affordance">#ffffffff</color>
 
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index f8b04ae..94fcc23 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -127,6 +127,10 @@
     <integer name="recents_animate_task_enter_from_home_duration">275</integer>
     <!-- The animation stagger to apply to each task animation when transitioning from home. -->
     <integer name="recents_animate_task_enter_from_home_delay">10</integer>
+    <!-- The short duration when animating in/out the lock to app button. -->
+    <integer name="recents_animate_lock_to_app_button_short_duration">150</integer>
+    <!-- The long duration when animating in/out the lock to app button. -->
+    <integer name="recents_animate_lock_to_app_button_long_duration">300</integer>
     <!-- The min animation duration for animating the nav bar scrim in. -->
     <integer name="recents_nav_bar_scrim_enter_duration">400</integer>
     <!-- The animation duration for animating the removal of a task view. -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index e20947f..e86aa0a 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -210,6 +210,9 @@
     <!-- The amount of highlight to make on each task view. -->
     <dimen name="recents_task_view_highlight">1dp</dimen>
 
+    <!-- The height of the lock-to-app button. -->
+    <dimen name="recents_task_view_lock_to_app_button_height">48dp</dimen>
+
     <!-- The height of a task view bar. -->
     <dimen name="recents_task_bar_height">56dp</dimen>
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 848fdf8..48670fb 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -563,6 +563,8 @@
     <string name="recents_empty_message">No recent apps</string>
     <!-- Recents: The info panel app info button string. [CHAR LIMIT=NONE] -->
     <string name="recents_app_info_button_label">Application Info</string>
+    <!-- Recents: The lock-to-app button. [CHAR LIMIT=NONE] -->
+    <string name="recents_lock_to_app_button_label">lock to app</string>
     <!-- Recents: Temporary string for the button in the recents search bar. [CHAR LIMIT=NONE] -->
     <string name="recents_search_bar_label">search</string>
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
index e375433..a9a606f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
@@ -68,6 +68,7 @@
     // Recents service binding
     Handler mHandler;
     boolean mBootCompleted = false;
+    boolean mStartAnimationTriggered = false;
 
     // Task launching
     RecentsConfiguration mConfig;
@@ -252,6 +253,7 @@
      * Creates the activity options for a unknown state->recents transition.
      */
     ActivityOptions getUnknownTransitionActivityOptions() {
+        mStartAnimationTriggered = false;
         return ActivityOptions.makeCustomAnimation(mContext,
                 R.anim.recents_from_unknown_enter,
                 R.anim.recents_from_unknown_exit, mHandler, this);
@@ -261,6 +263,7 @@
      * Creates the activity options for a home->recents transition.
      */
     ActivityOptions getHomeTransitionActivityOptions() {
+        mStartAnimationTriggered = false;
         return ActivityOptions.makeCustomAnimation(mContext,
                 R.anim.recents_from_launcher_enter,
                 R.anim.recents_from_launcher_exit, mHandler, this);
@@ -279,6 +282,7 @@
             // Take the full screenshot
             sLastScreenshot = mSystemServicesProxy.takeAppScreenshot();
             if (sLastScreenshot != null) {
+                mStartAnimationTriggered = false;
                 return ActivityOptions.makeCustomAnimation(mContext,
                         R.anim.recents_from_app_enter,
                         R.anim.recents_from_app_exit, mHandler, this);
@@ -302,6 +306,7 @@
                 c.setBitmap(null);
                 // Recycle the old thumbnail
                 firstThumbnail.recycle();
+                mStartAnimationTriggered = false;
                 return ActivityOptions.makeThumbnailScaleDownAnimation(mStatusBarView,
                         thumbnail, toTaskRect.left, toTaskRect.top, this);
             }
@@ -449,9 +454,12 @@
     @Override
     public void onAnimationStarted() {
         // Notify recents to start the enter animation
-        Intent intent = new Intent(RecentsActivity.ACTION_START_ENTER_ANIMATION);
-        intent.setPackage(mContext.getPackageName());
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        mContext.sendBroadcast(intent);
+        if (!mStartAnimationTriggered) {
+            Intent intent = new Intent(RecentsActivity.ACTION_START_ENTER_ANIMATION);
+            intent.setPackage(mContext.getPackageName());
+            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+            mContext.sendBroadcast(intent);
+            mStartAnimationTriggered = true;
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index b039485..e62d989 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -92,6 +92,11 @@
     public int taskBarExitAnimDuration;
     public int taskBarDismissDozeDelaySeconds;
 
+    /** Lock to app */
+    public int taskViewLockToAppButtonHeight;
+    public int taskViewLockToAppShortAnimDuration;
+    public int taskViewLockToAppLongAnimDuration;
+
     /** Nav bar scrim */
     public int navBarScrimEnterDuration;
 
@@ -226,6 +231,14 @@
         taskBarDismissDozeDelaySeconds =
                 res.getInteger(R.integer.recents_task_bar_dismiss_delay_seconds);
 
+        // Lock to app
+        taskViewLockToAppButtonHeight =
+                res.getDimensionPixelSize(R.dimen.recents_task_view_lock_to_app_button_height);
+        taskViewLockToAppShortAnimDuration =
+                res.getInteger(R.integer.recents_animate_lock_to_app_button_short_duration);
+        taskViewLockToAppLongAnimDuration =
+                res.getInteger(R.integer.recents_animate_lock_to_app_button_long_duration);
+
         // Nav bar scrim
         navBarScrimEnterDuration =
                 res.getInteger(R.integer.recents_nav_bar_scrim_enter_duration);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index 05c0f58..b8beda6f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -17,8 +17,10 @@
 package com.android.systemui.recents.misc;
 
 import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
 import android.app.ActivityOptions;
 import android.app.AppGlobals;
+import android.app.IActivityManager;
 import android.app.SearchManager;
 import android.appwidget.AppWidgetHost;
 import android.appwidget.AppWidgetManager;
@@ -66,6 +68,7 @@
     final static String TAG = "SystemServicesProxy";
 
     ActivityManager mAm;
+    IActivityManager mIam;
     AppWidgetManager mAwm;
     PackageManager mPm;
     IPackageManager mIpm;
@@ -83,6 +86,7 @@
     /** Private constructor */
     public SystemServicesProxy(Context context) {
         mAm = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+        mIam = ActivityManagerNative.getDefault();
         mAwm = AppWidgetManager.getInstance(context);
         mPm = context.getPackageManager();
         mUm = (UserManager) context.getSystemService(Context.USER_SERVICE);
@@ -407,6 +411,17 @@
     }
 
     /**
+     * Locks the current task.
+     */
+    public void lockCurrentTask() {
+        if (mIam == null) return;
+
+        try {
+            mIam.startLockTaskModeOnCurrent();
+        } catch (RemoteException e) {}
+    }
+
+    /**
      * Takes a screenshot of the current surface.
      */
     public Bitmap takeScreenshot() {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index 94474e9..86e8981 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -429,7 +429,8 @@
 
             // Create a new task
             Task task = new Task(t.persistentId, (t.id > -1), t.baseIntent, 0, activityLabel,
-                    activityIcon, activityColor, t.userId, t.firstActiveTime, t.lastActiveTime);
+                    activityIcon, activityColor, t.userId, t.firstActiveTime, t.lastActiveTime,
+                    (i == (taskCount - 1)));
 
             // Preload the specified number of apps
             if (i >= (taskCount - preloadCount)) {
@@ -523,7 +524,7 @@
             if (info == null) continue;
 
             stack.addTask(new Task(t.persistentId, true, t.baseIntent, 0, null, null, 0, 0,
-                    t.firstActiveTime, t.lastActiveTime));
+                    t.firstActiveTime, t.lastActiveTime, (i == (taskCount - 1))));
         }
         stack.createSimulatedAffiliatedGroupings();
         return stack;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
index 0667e4c..88e9f40 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -87,6 +87,7 @@
     public int colorPrimaryGreyscale;
     public Bitmap thumbnail;
     public boolean isActive;
+    public boolean canLockToTask;
     public int userId;
 
     TaskCallbacks mCb;
@@ -97,7 +98,7 @@
 
     public Task(int id, boolean isActive, Intent intent, int taskAffiliation, String activityTitle,
                 Drawable activityIcon, int colorPrimary, int userId,
-                long firstActiveTime, long lastActiveTime) {
+                long firstActiveTime, long lastActiveTime, boolean canLockToTask) {
         this.key = new TaskKey(id, intent, userId, firstActiveTime, lastActiveTime);
         this.taskAffiliation = taskAffiliation;
         this.activityLabel = activityTitle;
@@ -105,6 +106,7 @@
         this.colorPrimary = colorPrimary;
         this.colorPrimaryGreyscale = Utilities.colorToGreyscale(colorPrimary);
         this.isActive = isActive;
+        this.canLockToTask = canLockToTask;
         this.userId = userId;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
index 1ed0edd..7dd15a6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -150,7 +150,7 @@
         /* Notifies when a task has been added to the stack */
         public void onStackTaskAdded(TaskStack stack, Task t);
         /* Notifies when a task has been removed from the stack */
-        public void onStackTaskRemoved(TaskStack stack, Task t);
+        public void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask);
         /** Notifies when the stack was filtered */
         public void onStackFiltered(TaskStack newStack, ArrayList<Task> curTasks, Task t);
         /** Notifies when the stack was un-filtered */
@@ -203,9 +203,15 @@
             if (group.getTaskCount() == 0) {
                 removeGroup(group);
             }
+            // Update the lock-to-app state
+            Task newFrontMostTask = getFrontMostTask();
+            t.canLockToTask = false;
+            if (newFrontMostTask != null) {
+                newFrontMostTask.canLockToTask = true;
+            }
             if (mCb != null) {
                 // Notify that a task has been removed
-                mCb.onStackTaskRemoved(this, t);
+                mCb.onStackTaskRemoved(this, t, newFrontMostTask);
             }
         }
     }
@@ -226,7 +232,7 @@
             }
             if (mCb != null) {
                 // Notify that a task has been removed
-                mCb.onStackTaskRemoved(this, t);
+                mCb.onStackTaskRemoved(this, t, null);
             }
         }
         mTaskList.set(tasks);
@@ -239,6 +245,7 @@
 
     /** Gets the front task */
     public Task getFrontMostTask() {
+        if (mTaskList.size() == 0) return null;
         return mTaskList.getTasks().get(mTaskList.size() - 1);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index 85afb32..99b012e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -34,9 +34,10 @@
 import android.view.View;
 import android.view.WindowInsets;
 import android.widget.FrameLayout;
-import com.android.systemui.recents.misc.Console;
 import com.android.systemui.recents.Constants;
 import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.misc.Console;
+import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.model.RecentsPackageMonitor;
 import com.android.systemui.recents.model.RecentsTaskLoader;
 import com.android.systemui.recents.model.SpaceNode;
@@ -144,7 +145,7 @@
                             Console.log(Constants.Log.UI.Focus, "[RecentsView|launchFocusedTask]",
                                     "Found focused Task");
                         }
-                        onTaskViewClicked(stackView, tv, stack, task);
+                        onTaskViewClicked(stackView, tv, stack, task, false);
                         return true;
                     }
                 }
@@ -180,7 +181,7 @@
                             tv = stv;
                         }
                     }
-                    onTaskViewClicked(stackView, tv, stack, task);
+                    onTaskViewClicked(stackView, tv, stack, task, false);
                     return true;
                 }
             }
@@ -431,7 +432,7 @@
 
     @Override
     public void onTaskViewClicked(final TaskStackView stackView, final TaskView tv,
-                                  final TaskStack stack, final Task task) {
+                                  final TaskStack stack, final Task task, final boolean lockToTask) {
         // Notify any callbacks of the launching of a new task
         if (mCb != null) {
             mCb.onTaskViewClicked();
@@ -456,6 +457,8 @@
         }
 
         // Compute the thumbnail to scale up from
+        final SystemServicesProxy ssp =
+                RecentsTaskLoader.getInstance().getSystemServicesProxy();
         ActivityOptions opts = null;
         int thumbnailWidth = transform.rect.width();
         int thumbnailHeight = transform.rect.height();
@@ -469,8 +472,26 @@
                     new Rect(0, 0, task.thumbnail.getWidth(), task.thumbnail.getHeight()),
                     new Rect(0, 0, thumbnailWidth, thumbnailHeight), null);
             c.setBitmap(null);
+            ActivityOptions.OnAnimationStartedListener animStartedListener = null;
+            if (lockToTask) {
+                animStartedListener = new ActivityOptions.OnAnimationStartedListener() {
+                    boolean mTriggered = false;
+                    @Override
+                    public void onAnimationStarted() {
+                        if (!mTriggered) {
+                            postDelayed(new Runnable() {
+                                @Override
+                                public void run() {
+                                    ssp.lockCurrentTask();
+                                }
+                            }, 350);
+                            mTriggered = true;
+                        }
+                    }
+                };
+            }
             opts = ActivityOptions.makeThumbnailScaleUpAnimation(sourceView,
-                    b, offsetX, offsetY);
+                    b, offsetX, offsetY, animStartedListener);
         }
 
         final ActivityOptions launchOpts = opts;
@@ -496,8 +517,12 @@
                         UserHandle taskUser = new UserHandle(task.userId);
                         if (launchOpts != null) {
                             getContext().startActivityAsUser(i, launchOpts.toBundle(), taskUser);
+
                         } else {
                             getContext().startActivityAsUser(i, taskUser);
+                            if (lockToTask) {
+                                ssp.lockCurrentTask();
+                            }
                         }
                     } catch (ActivityNotFoundException anfe) {
                         Console.logError(getContext(), "Could not start Activity");
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 35cf8ab..599c590 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -51,11 +51,12 @@
 /* The visual representation of a task stack view */
 public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCallbacks,
         TaskView.TaskViewCallbacks, ViewPool.ViewPoolConsumer<TaskView, Task>,
-        View.OnClickListener, RecentsPackageMonitor.PackageCallbacks {
+        RecentsPackageMonitor.PackageCallbacks {
 
     /** The TaskView callbacks */
     interface TaskStackViewCallbacks {
-        public void onTaskViewClicked(TaskStackView stackView, TaskView tv, TaskStack stack, Task t);
+        public void onTaskViewClicked(TaskStackView stackView, TaskView tv, TaskStack stack, Task t,
+                                      boolean lockToTask);
         public void onTaskViewAppInfoClicked(Task t);
         public void onTaskViewDismissed(Task t);
         public void onAllTaskViewsDismissed();
@@ -734,7 +735,8 @@
         for (int i = 0; i < childCount; i++) {
             TaskView t = (TaskView) getChildAt(i);
             t.measure(MeasureSpec.makeMeasureSpec(mStackAlgorithm.mTaskRect.width(), MeasureSpec.EXACTLY),
-                    MeasureSpec.makeMeasureSpec(mStackAlgorithm.mTaskRect.height(), MeasureSpec.EXACTLY));
+                    MeasureSpec.makeMeasureSpec(mStackAlgorithm.mTaskRect.height() +
+                            mConfig.taskViewLockToAppButtonHeight, MeasureSpec.EXACTLY));
         }
 
         setMeasuredDimension(width, height);
@@ -766,7 +768,7 @@
             TaskView t = (TaskView) getChildAt(i);
             t.layout(mStackAlgorithm.mTaskRect.left, mStackAlgorithm.mStackRectSansPeek.top,
                     mStackAlgorithm.mTaskRect.right, mStackAlgorithm.mStackRectSansPeek.top +
-                    mStackAlgorithm.mTaskRect.height());
+                    mStackAlgorithm.mTaskRect.height() + mConfig.taskViewLockToAppButtonHeight);
         }
 
         if (mAwaitingFirstLayout) {
@@ -903,11 +905,6 @@
         mUIDozeTrigger.poke();
     }
 
-    /** Disables handling touch on this task view. */
-    void setTouchOnTaskView(TaskView tv, boolean enabled) {
-        tv.setOnClickListener(enabled ? this : null);
-    }
-
     /**** TaskStackCallbacks Implementation ****/
 
     @Override
@@ -919,25 +916,33 @@
     }
 
     @Override
-    public void onStackTaskRemoved(TaskStack stack, Task t) {
+    public void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask) {
         // Update the task offsets
         mStackAlgorithm.updateTaskOffsets(mStack.getTasks());
 
         // Remove the view associated with this task, we can't rely on updateTransforms
         // to work here because the task is no longer in the list
-        TaskView tv = getChildViewForTask(t);
+        TaskView tv = getChildViewForTask(removedTask);
         if (tv != null) {
             mViewPool.returnViewToPool(tv);
         }
 
         // Notify the callback that we've removed the task and it can clean up after it
-        mCb.onTaskViewDismissed(t);
+        mCb.onTaskViewDismissed(removedTask);
 
         // Update the min/max scroll and animate other task views into their new positions
         updateMinMaxScroll(true);
         int movement = (int) mStackAlgorithm.getTaskOverlapHeight();
         requestSynchronizeStackViewsWithModel(Utilities.calculateTranslationAnimationDuration(movement));
 
+        // Update the new front most task
+        if (newFrontMostTask != null) {
+            TaskView frontTv = getChildViewForTask(newFrontMostTask);
+            if (frontTv != null) {
+                frontTv.onTaskBound(newFrontMostTask);
+            }
+        }
+
         // If there are no remaining tasks, then either unfilter the current stack, or just close
         // the activity if there are no filtered stacks
         if (mStack.getTaskCount() == 0) {
@@ -1086,7 +1091,7 @@
             addView(tv, insertIndex);
 
             // Set the callbacks and listeners for this new view
-            setTouchOnTaskView(tv, true);
+            tv.setTouchEnabled(true);
             tv.setCallbacks(this);
         } else {
             attachViewToParent(tv, insertIndex, tv.getLayoutParams());
@@ -1129,18 +1134,7 @@
     }
 
     @Override
-    public void onTaskViewDismissed(TaskView tv) {
-        Task task = tv.getTask();
-        // Remove the task from the view
-        mStack.removeTask(task);
-    }
-
-    /**** View.OnClickListener Implementation ****/
-
-    @Override
-    public void onClick(View v) {
-        TaskView tv = (TaskView) v;
-        Task task = tv.getTask();
+    public void onTaskViewClicked(TaskView tv, Task task, boolean lockToTask) {
         if (Console.Enabled) {
             Console.log(Constants.Log.UI.ClickEvents, "[TaskStack|Clicked|Thumbnail]",
                     task + " cb: " + mCb);
@@ -1150,10 +1144,17 @@
         mUIDozeTrigger.stopDozing();
 
         if (mCb != null) {
-            mCb.onTaskViewClicked(this, tv, mStack, task);
+            mCb.onTaskViewClicked(this, tv, mStack, task, lockToTask);
         }
     }
 
+    @Override
+    public void onTaskViewDismissed(TaskView tv) {
+        Task task = tv.getTask();
+        // Remove the task from the view
+        mStack.removeTask(task);
+    }
+
     /**** RecentsPackageMonitor.PackageCallbacks Implementation ****/
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
index 789b4f7..e1e682b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
@@ -95,9 +95,11 @@
 
         if (numTasks <= 1) {
             // If there is only one task, then center the task in the stack rect (sans peek)
-            mMinScroll = mMaxScroll = -(stackHeight - taskHeight) / 2;
+            mMinScroll = mMaxScroll = -(stackHeight -
+                    (taskHeight + mConfig.taskViewLockToAppButtonHeight)) / 2;
         } else {
-            int maxScrollHeight = taskHeight + getStackScrollForTaskIndex(tasks.get(tasks.size() - 1));
+            int maxScrollHeight = getStackScrollForTaskIndex(tasks.get(tasks.size() - 1))
+                    + taskHeight + mConfig.taskViewLockToAppButtonHeight;
             mMinScroll = Math.min(stackHeight, maxScrollHeight) - stackHeight;
             mMaxScroll = maxScrollHeight - stackHeight;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
index e186e2e..15ace13 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -377,7 +377,9 @@
         // Enable HW layers on that task
         tv.enableHwLayers();
         // Disallow touch events from this task view
-        mSv.setTouchOnTaskView(tv, false);
+        tv.setTouchEnabled(false);
+        // Hide the footer
+        tv.animateFooterVisibility(false, mSv.mConfig.taskViewLockToAppShortAnimDuration, 0);
         // Disallow parents from intercepting touch events
         final ViewParent parent = mSv.getParent();
         if (parent != null) {
@@ -413,7 +415,9 @@
         // Re-enable clipping with the stack
         tv.setClipViewInStack(true);
         // Re-enable touch events from this task view
-        mSv.setTouchOnTaskView(tv, true);
+        tv.setTouchEnabled(true);
+        // Restore the footer
+        tv.animateFooterVisibility(true, mSv.mConfig.taskViewLockToAppShortAnimDuration, 0);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index 33e3f58..125b018 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -35,6 +35,7 @@
 import com.android.systemui.recents.Constants;
 import com.android.systemui.recents.RecentsConfiguration;
 import com.android.systemui.recents.model.Task;
+import com.android.systemui.recents.model.TaskStack;
 
 
 /* A task view */
@@ -44,11 +45,16 @@
     interface TaskViewCallbacks {
         public void onTaskViewAppIconClicked(TaskView tv);
         public void onTaskViewAppInfoClicked(TaskView tv);
+        public void onTaskViewClicked(TaskView tv, Task t, boolean lockToTask);
         public void onTaskViewDismissed(TaskView tv);
     }
 
     RecentsConfiguration mConfig;
 
+    int mFooterHeight;
+    int mMaxFooterHeight;
+    ObjectAnimator mFooterAnimator;
+
     int mDim;
     int mMaxDim;
     AccelerateInterpolator mDimInterpolator = new AccelerateInterpolator();
@@ -60,9 +66,11 @@
     boolean mClipViewInStack;
     Rect mTmpRect = new Rect();
     Paint mLayerPaint = new Paint();
+    Outline mOutline = new Outline();
 
     TaskThumbnailView mThumbnailView;
     TaskBarView mBarView;
+    View mLockToAppButtonView;
     TaskViewCallbacks mCb;
 
     // Optimizations
@@ -102,9 +110,11 @@
     public TaskView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
         mConfig = RecentsConfiguration.getInstance();
+        mMaxFooterHeight = mConfig.taskViewLockToAppButtonHeight;
         setWillNotDraw(false);
         setClipToOutline(true);
         setDim(getDim());
+        setFooterHeight(getFooterHeight());
     }
 
     @Override
@@ -117,6 +127,7 @@
         // Bind the views
         mBarView = (TaskBarView) findViewById(R.id.task_view_bar);
         mThumbnailView = (TaskThumbnailView) findViewById(R.id.task_view_thumbnail);
+        mLockToAppButtonView = findViewById(R.id.lock_to_app);
 
         if (mTaskDataLoaded) {
             onTaskDataLoaded();
@@ -125,13 +136,33 @@
 
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        int width = MeasureSpec.getSize(widthMeasureSpec);
+        int height = MeasureSpec.getSize(heightMeasureSpec);
 
-        // Update the outline
-        Outline o = new Outline();
-        o.setRoundRect(0, 0, getMeasuredWidth(), getMeasuredHeight(),
+        // Measure the bar view, thumbnail, and lock-to-app buttons
+        mBarView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+                MeasureSpec.makeMeasureSpec(mConfig.taskBarHeight, MeasureSpec.EXACTLY));
+        mLockToAppButtonView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+                MeasureSpec.makeMeasureSpec(mConfig.taskViewLockToAppButtonHeight,
+                        MeasureSpec.EXACTLY));
+        // Measure the thumbnail height to be the same as the width
+        mThumbnailView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+                MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY));
+        setMeasuredDimension(width, height);
+        updateOutline();
+    }
+
+    /** Updates the outline to match whether the lock-to-app button is visible or not. */
+    void updateOutline() {
+        int height = getMeasuredHeight();
+        if (height == 0) return;
+
+        // Account for the current footer height
+        height = height - mMaxFooterHeight + mFooterHeight;
+
+        mOutline.setRoundRect(0, 0, getMeasuredWidth(), height,
                 mConfig.taskViewRoundedCornerRadiusPx);
-        setOutline(o);
+        setOutline(mOutline);
     }
 
     /** Set callback */
@@ -289,6 +320,8 @@
                         // Animate the task bar of the first task view
                         mBarView.startEnterRecentsAnimation(0, mEnableThumbnailClip);
                         setVisibility(View.VISIBLE);
+                        // Animate the footer into view
+                        animateFooterVisibility(true, mConfig.taskBarEnterAnimDuration, 0);
                         // Decrement the post animation trigger
                         ctx.postAnimationTrigger.decrement();
                     }
@@ -335,6 +368,10 @@
                 });
                 anim.start();
                 ctx.postAnimationTrigger.increment();
+
+                // Animate the footer into view
+                animateFooterVisibility(true, mConfig.taskBarEnterAnimDuration,
+                        mConfig.taskBarEnterAnimDelay);
             } else {
                 mEnableThumbnailClip.run();
             }
@@ -366,9 +403,16 @@
                     })
                     .start();
             ctx.postAnimationTrigger.increment();
+
+            // Animate the footer into view
+            animateFooterVisibility(true, mConfig.taskViewEnterFromHomeDuration,
+                    mConfig.taskBarEnterAnimDelay);
         } else {
             // Otherwise, just enable the thumbnail clip
             mEnableThumbnailClip.run();
+
+            // Animate the footer into view
+            animateFooterVisibility(true, 0, 0);
         }
     }
 
@@ -457,12 +501,14 @@
     void enableHwLayers() {
         mThumbnailView.setLayerType(View.LAYER_TYPE_HARDWARE, mLayerPaint);
         mBarView.enableHwLayers();
+        mLockToAppButtonView.setLayerType(View.LAYER_TYPE_HARDWARE, mLayerPaint);
     }
 
     /** Disable the hw layers on this task view */
     void disableHwLayers() {
         mThumbnailView.setLayerType(View.LAYER_TYPE_NONE, mLayerPaint);
         mBarView.disableHwLayers();
+        mLockToAppButtonView.setLayerType(View.LAYER_TYPE_NONE, mLayerPaint);
     }
 
     /** Sets the stubbed state of this task view. */
@@ -499,6 +545,57 @@
         }
     }
 
+    /** Sets the footer height. */
+    public void setFooterHeight(int height) {
+        mFooterHeight = height;
+        updateOutline();
+        invalidate(0, getMeasuredHeight() - mMaxFooterHeight, getMeasuredWidth(),
+                getMeasuredHeight());
+    }
+
+    /** Gets the footer height. */
+    public int getFooterHeight() {
+        return mFooterHeight;
+    }
+
+    /** Animates the footer into and out of view. */
+    public void animateFooterVisibility(boolean visible, int duration, int delay) {
+        if (!mTask.canLockToTask) return;
+        if (mMaxFooterHeight <= 0) return;
+
+        if (mFooterAnimator != null) {
+            mFooterAnimator.removeAllListeners();
+            mFooterAnimator.cancel();
+        }
+        int height = visible ? mMaxFooterHeight : 0;
+        if (visible && mLockToAppButtonView.getVisibility() != View.VISIBLE) {
+            if (duration > 0) {
+                setFooterHeight(0);
+            } else {
+                setFooterHeight(mMaxFooterHeight);
+            }
+            mLockToAppButtonView.setVisibility(View.VISIBLE);
+        }
+        if (duration > 0) {
+            mFooterAnimator = ObjectAnimator.ofInt(this, "footerHeight", height);
+            mFooterAnimator.setDuration(duration);
+            mFooterAnimator.setInterpolator(mConfig.fastOutSlowInInterpolator);
+            if (!visible) {
+                mFooterAnimator.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        mLockToAppButtonView.setVisibility(View.INVISIBLE);
+                    }
+                });
+            }
+            mFooterAnimator.start();
+        } else {
+            if (!visible) {
+                mLockToAppButtonView.setVisibility(View.INVISIBLE);
+            }
+        }
+    }
+
     /** Returns the current dim. */
     public void setDim(int dim) {
         mDim = dim;
@@ -584,6 +681,11 @@
     public void onTaskBound(Task t) {
         mTask = t;
         mTask.setCallbacks(this);
+        if (getMeasuredWidth() == 0) {
+            animateFooterVisibility(t.canLockToTask, 0, 0);
+        } else {
+            animateFooterVisibility(t.canLockToTask, mConfig.taskViewLockToAppLongAnimDuration, 0);
+        }
     }
 
     @Override
@@ -597,6 +699,7 @@
                 mBarView.mApplicationIcon.setOnClickListener(this);
             }
             mBarView.mDismissButton.setOnClickListener(this);
+            mLockToAppButtonView.setOnClickListener(this);
             if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) {
                 if (mConfig.developerOptionsEnabled) {
                     mBarView.mApplicationIcon.setOnLongClickListener(this);
@@ -618,6 +721,7 @@
                 mBarView.mApplicationIcon.setOnClickListener(null);
             }
             mBarView.mDismissButton.setOnClickListener(null);
+            mLockToAppButtonView.setOnClickListener(null);
             if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) {
                 mBarView.mApplicationIcon.setOnLongClickListener(null);
             }
@@ -625,6 +729,11 @@
         mTaskDataLoaded = false;
     }
 
+    /** Enables/disables handling touch on this task view. */
+    void setTouchEnabled(boolean enabled) {
+        setOnClickListener(enabled ? this : null);
+    }
+
     @Override
     public void onClick(final View v) {
         // We purposely post the handler delayed to allow for the touch feedback to draw
@@ -642,6 +751,10 @@
                             mCb.onTaskViewDismissed(tv);
                         }
                     });
+                    // Hide the footer
+                    tv.animateFooterVisibility(false, mConfig.taskViewRemoveAnimDuration, 0);
+                } else if (v == tv || v == mLockToAppButtonView) {
+                    mCb.onTaskViewClicked(tv, tv.getTask(), (v == mLockToAppButtonView));
                 }
             }
         }, 125);
diff --git a/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java b/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java
index 2c8c1c1..432424b 100644
--- a/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java
+++ b/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java
@@ -66,7 +66,7 @@
         }
         HdmiCecDeviceInfo device = mService.getDeviceInfo(activeAddress);
         if (device == null) {
-            tv.addAndStartAction(new NewDeviceAction(tv, activeAddress, activePath));
+            tv.startNewDeviceAction(activeAddress, activePath);
         }
 
         int currentActive = tv.getActiveSource();
diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java
index 1cdf44c..3028100 100644
--- a/services/core/java/com/android/server/hdmi/Constants.java
+++ b/services/core/java/com/android/server/hdmi/Constants.java
@@ -203,5 +203,8 @@
     //       in config.xml to allow customization.
     static final int IRT_MS = 300;
 
+    static final String PROPERTY_PREFERRED_ADDRESS_PLAYBACK = "hdmi_cec.preferred_address.playback";
+    static final String PROPERTY_PREFERRED_ADDRESS_TV = "hdmi_cec.preferred_address.tv";
+
     private Constants() { /* cannot be instantiated */ }
 }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index 14d9b75..72519f2 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -265,8 +265,6 @@
     @ServiceThreadOnly
     void clearLogicalAddress() {
         assertRunOnServiceThread();
-        // TODO: consider to backup logical address so that new logical address
-        // allocation can use it as preferred address.
         for (int i = 0; i < mLocalDevices.size(); ++i) {
             mLocalDevices.valueAt(i).clearAddress();
         }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index 8f3c551..f1e7ff2 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -20,6 +20,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.SystemProperties;
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
@@ -108,8 +109,7 @@
     @ServiceThreadOnly
     void init() {
         assertRunOnServiceThread();
-        mPreferredAddress = Constants.ADDR_UNREGISTERED;
-        // TODO: load preferred address from permanent storage.
+        mPreferredAddress = getPreferredAddress();
     }
 
     /**
@@ -118,6 +118,16 @@
     protected abstract void onAddressAllocated(int logicalAddress, boolean fromBootup);
 
     /**
+     * Get the preferred logical address from system properties.
+     */
+    protected abstract int getPreferredAddress();
+
+    /**
+     * Set the preferred logical address to system properties.
+     */
+    protected abstract void setPreferredAddress(int addr);
+
+    /**
      * Dispatch incoming message.
      *
      * @param message incoming message
@@ -398,6 +408,7 @@
         assertRunOnServiceThread();
         mAddress = mPreferredAddress = logicalAddress;
         onAddressAllocated(logicalAddress, fromBootup);
+        setPreferredAddress(logicalAddress);
     }
 
     @ServiceThreadOnly
@@ -427,18 +438,6 @@
     }
 
     @ServiceThreadOnly
-    void setPreferredAddress(int addr) {
-        assertRunOnServiceThread();
-        mPreferredAddress = addr;
-    }
-
-    @ServiceThreadOnly
-    int getPreferredAddress() {
-        assertRunOnServiceThread();
-        return mPreferredAddress;
-    }
-
-    @ServiceThreadOnly
     void addAndStartAction(final FeatureAction action) {
         assertRunOnServiceThread();
         if (mService.isPowerStandbyOrTransient()) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index 06907ce..a66d78c 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -20,6 +20,7 @@
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.IHdmiControlCallback;
 import android.os.RemoteException;
+import android.os.SystemProperties;
 import android.util.Slog;
 
 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
@@ -44,6 +45,22 @@
                 mAddress, mService.getPhysicalAddress(), mDeviceType));
     }
 
+    @Override
+    @ServiceThreadOnly
+    protected int getPreferredAddress() {
+        assertRunOnServiceThread();
+        return SystemProperties.getInt(Constants.PROPERTY_PREFERRED_ADDRESS_PLAYBACK,
+                Constants.ADDR_UNREGISTERED);
+    }
+
+    @Override
+    @ServiceThreadOnly
+    protected void setPreferredAddress(int addr) {
+        assertRunOnServiceThread();
+        SystemProperties.set(Constants.PROPERTY_PREFERRED_ADDRESS_PLAYBACK,
+                String.valueOf(addr));
+    }
+
     @ServiceThreadOnly
     void oneTouchPlay(IHdmiControlCallback callback) {
         assertRunOnServiceThread();
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index da873ab..44b0f28 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -26,6 +26,7 @@
 import android.media.AudioPort;
 import android.media.AudioSystem;
 import android.os.RemoteException;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.provider.Settings.Global;
 import android.util.Slog;
@@ -108,6 +109,22 @@
         // TODO: unregister audio port update listener if local device is released.
     }
 
+    @Override
+    @ServiceThreadOnly
+    protected int getPreferredAddress() {
+        assertRunOnServiceThread();
+        return SystemProperties.getInt(Constants.PROPERTY_PREFERRED_ADDRESS_TV,
+                Constants.ADDR_UNREGISTERED);
+    }
+
+    @Override
+    @ServiceThreadOnly
+    protected void setPreferredAddress(int addr) {
+        assertRunOnServiceThread();
+        SystemProperties.set(Constants.PROPERTY_PREFERRED_ADDRESS_TV,
+                String.valueOf(addr));
+    }
+
     private void registerAudioPortUpdateListener() {
         mService.getAudioManager().registerAudioPortUpdateListener(
                 new OnAudioPortUpdateListener() {
@@ -145,6 +162,11 @@
             handleSelectInternalSource(callback);
             return;
         }
+        if (!mService.isControlEnabled()) {
+            setActiveSource(targetAddress);
+            invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
+            return;
+        }
         HdmiCecDeviceInfo targetDevice = getDeviceInfo(targetAddress);
         if (targetDevice == null) {
             invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
@@ -223,18 +245,23 @@
     void doManualPortSwitching(int portId, IHdmiControlCallback callback) {
         assertRunOnServiceThread();
         // Seq #20
-        if (!mService.isControlEnabled() || portId == getActivePortId()) {
+        if (!mService.isValidPortId(portId)) {
             invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
             return;
         }
-        // TODO: Make sure this call does not stem from <Active Source> message reception.
-
+        if (!mService.isControlEnabled()) {
+            setActivePortId(portId);
+            invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
+            return;
+        }
+        if (portId == getActivePortId()) {
+            invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
+            return;
+        }
         setActivePortId(portId);
         // TODO: Return immediately if the operation is triggered by <Text/Image View On>
-        //       and this is the first notification about the active input after power-on.
-        // TODO: Handle invalid port id / active input which should be treated as an
-        //       internal tuner.
-
+        //       and this is the first notification about the active input after power-on
+        //       (switch to HDMI didn't happen so far but is expected to happen soon).
         removeAction(RoutingControlAction.class);
 
         int oldPath = mService.portIdToPath(mService.portIdToPath(getActivePortId()));
@@ -372,10 +399,28 @@
         if (!isInDeviceList(path, address)) {
             handleNewDeviceAtTheTailOfActivePath(path);
         }
-        addAndStartAction(new NewDeviceAction(this, address, path));
+        startNewDeviceAction(address, path);
         return true;
     }
 
+    void startNewDeviceAction(int address, int path) {
+        for (NewDeviceAction action : getActions(NewDeviceAction.class)) {
+            // If there is new device action which has the same logical address and path
+            // ignore new request.
+            // NewDeviceAction is created whenever it receives <Report Physical Address>.
+            // And there is a chance starting NewDeviceAction for the same source.
+            // Usually, new device sends <Report Physical Address> when it's plugged
+            // in. However, TV can detect a new device from HotPlugDetectionAction,
+            // which sends <Give Physical Address> to the source for newly detected
+            // device.
+            if (action.isActionOf(address, path)) {
+                return;
+            }
+        }
+
+        addAndStartAction(new NewDeviceAction(this, address, path));
+    }
+
     private void handleNewDeviceAtTheTailOfActivePath(int path) {
         // Seq #22
         if (isTailOfActivePath(path, getActivePath())) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index c04b2ba..a9a391b 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -398,6 +398,15 @@
         return Constants.INVALID_PORT_ID;
     }
 
+    boolean isValidPortId(int portId) {
+        for (HdmiPortInfo info : mPortInfo) {
+            if (portId == info.getId()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * Returns {@link Looper} for IO operation.
      *
diff --git a/services/core/java/com/android/server/hdmi/NewDeviceAction.java b/services/core/java/com/android/server/hdmi/NewDeviceAction.java
index 80deaab..f89e299 100644
--- a/services/core/java/com/android/server/hdmi/NewDeviceAction.java
+++ b/services/core/java/com/android/server/hdmi/NewDeviceAction.java
@@ -56,7 +56,6 @@
      * @param source {@link HdmiCecLocalDevice} instance
      * @param deviceLogicalAddress logical address of the device in interest
      * @param devicePhysicalAddress physical address of the device in interest
-     * @param requireRoutingChange whether to initiate routing change or not
      */
     NewDeviceAction(HdmiCecLocalDevice source, int deviceLogicalAddress,
             int devicePhysicalAddress) {
@@ -68,18 +67,6 @@
 
     @Override
     public boolean start() {
-        if (HdmiUtils.getTypeFromAddress(getSourceAddress())
-                == HdmiCecDeviceInfo.DEVICE_AUDIO_SYSTEM) {
-            if (tv().getAvrDeviceInfo() == null) {
-                // TODO: Start system audio initiation action
-            }
-
-            if (shouldTryArcInitiation()) {
-                addAndStartAction(new RequestArcInitiationAction(localDevice(),
-                                mDeviceLogicalAddress));
-            }
-        }
-
         mState = STATE_WAITING_FOR_SET_OSD_NAME;
         if (mayProcessCommandIfCached(mDeviceLogicalAddress, Constants.MESSAGE_SET_OSD_NAME)) {
             return true;
@@ -91,10 +78,6 @@
         return true;
     }
 
-    private boolean shouldTryArcInitiation() {
-         return tv().isConnectedToArcPort(mDevicePhysicalAddress) && tv().isArcFeatureEnabled();
-    }
-
     @Override
     public boolean processCommand(HdmiCecMessage cmd) {
         // For the logical device in interest, we want two more pieces of information -
@@ -172,6 +155,23 @@
                 mDeviceLogicalAddress, mDevicePhysicalAddress,
                 HdmiUtils.getTypeFromAddress(mDeviceLogicalAddress),
                 mVendorId, mDisplayName));
+
+        if (HdmiUtils.getTypeFromAddress(mDeviceLogicalAddress)
+                == HdmiCecDeviceInfo.DEVICE_AUDIO_SYSTEM) {
+            if (tv().getSystemAudioMode()) {
+                addAndStartAction(new SystemAudioAutoInitiationAction(localDevice(),
+                        mDeviceLogicalAddress));
+            }
+
+            if (shouldTryArcInitiation()) {
+                addAndStartAction(new RequestArcInitiationAction(localDevice(),
+                        mDeviceLogicalAddress));
+            }
+        }
+    }
+
+    private boolean shouldTryArcInitiation() {
+        return tv().isConnectedToArcPort(mDevicePhysicalAddress) && tv().isArcFeatureEnabled();
     }
 
     @Override
@@ -188,4 +188,8 @@
             finish();
         }
     }
+
+    boolean isActionOf(int address, int path) {
+        return (mDeviceLogicalAddress == address) && (mDevicePhysicalAddress == path);
+    }
 }
diff --git a/services/core/java/com/android/server/pm/KeySetManagerService.java b/services/core/java/com/android/server/pm/KeySetManagerService.java
index 871c94f..c19951f 100644
--- a/services/core/java/com/android/server/pm/KeySetManagerService.java
+++ b/services/core/java/com/android/server/pm/KeySetManagerService.java
@@ -52,13 +52,11 @@
     /** Sentinel value returned when public key is not found. */
     protected static final long PUBLIC_KEY_NOT_FOUND = -1;
 
-    private final Object mLockObject = new Object();
-
     private final LongSparseArray<KeySet> mKeySets;
 
     private final LongSparseArray<PublicKey> mPublicKeys;
 
-    protected final LongSparseArray<Set<Long>> mKeySetMapping;
+    protected final LongSparseArray<ArraySet<Long>> mKeySetMapping;
 
     private final Map<String, PackageSetting> mPackages;
 
@@ -69,7 +67,7 @@
     public KeySetManagerService(Map<String, PackageSetting> packages) {
         mKeySets = new LongSparseArray<KeySet>();
         mPublicKeys = new LongSparseArray<PublicKey>();
-        mKeySetMapping = new LongSparseArray<Set<Long>>();
+        mKeySetMapping = new LongSparseArray<ArraySet<Long>>();
         mPackages = packages;
     }
 
@@ -84,18 +82,16 @@
      *
      * Note that this can return true for multiple KeySets.
      */
-    public boolean packageIsSignedBy(String packageName, KeySet ks) {
-        synchronized (mLockObject) {
-            PackageSetting pkg = mPackages.get(packageName);
-            if (pkg == null) {
-                throw new NullPointerException("Invalid package name");
-            }
-            if (pkg.keySetData == null) {
-                throw new NullPointerException("Package has no KeySet data");
-            }
-            long id = getIdByKeySetLocked(ks);
-            return pkg.keySetData.packageIsSignedBy(id);
+    public boolean packageIsSignedByLPr(String packageName, KeySet ks) {
+        PackageSetting pkg = mPackages.get(packageName);
+        if (pkg == null) {
+            throw new NullPointerException("Invalid package name");
         }
+        if (pkg.keySetData == null) {
+            throw new NullPointerException("Package has no KeySet data");
+        }
+        long id = getIdByKeySetLPr(ks);
+        return pkg.keySetData.packageIsSignedBy(id);
     }
 
     /**
@@ -103,22 +99,20 @@
      * in its manifest that a) contains the given keys and b) is named
      * alias by that package.
      */
-    public void addDefinedKeySetToPackage(String packageName,
+    public void addDefinedKeySetToPackageLPw(String packageName,
             Set<PublicKey> keys, String alias) {
         if ((packageName == null) || (keys == null) || (alias == null)) {
             Slog.w(TAG, "Got null argument for a defined keyset, ignoring!");
             return;
         }
-        synchronized (mLockObject) {
-            PackageSetting pkg = mPackages.get(packageName);
-            if (pkg == null) {
-                throw new NullPointerException("Unknown package");
-            }
-            // Add to KeySets, then to package
-            KeySet ks = addKeySetLocked(keys);
-            long id = getIdByKeySetLocked(ks);
-            pkg.keySetData.addDefinedKeySet(id, alias);
+        PackageSetting pkg = mPackages.get(packageName);
+        if (pkg == null) {
+            throw new NullPointerException("Unknown package");
         }
+        // Add to KeySets, then to package
+        KeySet ks = addKeySetLPw(keys);
+        long id = getIdByKeySetLPr(ks);
+        pkg.keySetData.addDefinedKeySet(id, alias);
     }
 
     /**
@@ -126,53 +120,49 @@
      * alias in its manifest to be an upgradeKeySet.  This must be called
      * after all of the defined KeySets have been added.
      */
-    public void addUpgradeKeySetToPackage(String packageName, String alias) {
+    public void addUpgradeKeySetToPackageLPw(String packageName, String alias) {
         if ((packageName == null) || (alias == null)) {
             Slog.w(TAG, "Got null argument for a defined keyset, ignoring!");
             return;
         }
-        synchronized (mLockObject) {
-            PackageSetting pkg = mPackages.get(packageName);
-            if (pkg == null) {
-                throw new NullPointerException("Unknown package");
-            }
-            pkg.keySetData.addUpgradeKeySet(alias);
+        PackageSetting pkg = mPackages.get(packageName);
+        if (pkg == null) {
+            throw new NullPointerException("Unknown package");
         }
+        pkg.keySetData.addUpgradeKeySet(alias);
     }
 
     /**
      * Similar to the above, this informs the system that the given package
      * was signed by the provided KeySet.
      */
-    public void addSigningKeySetToPackage(String packageName,
+    public void addSigningKeySetToPackageLPw(String packageName,
             Set<PublicKey> signingKeys) {
         if ((packageName == null) || (signingKeys == null)) {
             Slog.w(TAG, "Got null argument for a signing keyset, ignoring!");
             return;
         }
-        synchronized (mLockObject) {
-            // add the signing KeySet
-            KeySet ks = addKeySetLocked(signingKeys);
-            long id = getIdByKeySetLocked(ks);
-            Set<Long> publicKeyIds = mKeySetMapping.get(id);
-            if (publicKeyIds == null) {
-                throw new NullPointerException("Got invalid KeySet id");
-            }
+        // add the signing KeySet
+        KeySet ks = addKeySetLPw(signingKeys);
+        long id = getIdByKeySetLPr(ks);
+        Set<Long> publicKeyIds = mKeySetMapping.get(id);
+        if (publicKeyIds == null) {
+            throw new NullPointerException("Got invalid KeySet id");
+        }
 
-            // attach it to the package
-            PackageSetting pkg = mPackages.get(packageName);
-            if (pkg == null) {
-                throw new NullPointerException("No such package!");
-            }
-            pkg.keySetData.setProperSigningKeySet(id);
-            // for each KeySet which is a subset of the one above, add the
-            // KeySet id to the package's signing KeySets
-            for (int keySetIndex = 0; keySetIndex < mKeySets.size(); keySetIndex++) {
-                long keySetID = mKeySets.keyAt(keySetIndex);
-                Set<Long> definedKeys = mKeySetMapping.get(keySetID);
-                if (publicKeyIds.containsAll(definedKeys)) {
-                    pkg.keySetData.addSigningKeySet(keySetID);
-                }
+        // attach it to the package
+        PackageSetting pkg = mPackages.get(packageName);
+        if (pkg == null) {
+            throw new NullPointerException("No such package!");
+        }
+        pkg.keySetData.setProperSigningKeySet(id);
+        // for each KeySet which is a subset of the one above, add the
+        // KeySet id to the package's signing KeySets
+        for (int keySetIndex = 0; keySetIndex < mKeySets.size(); keySetIndex++) {
+            long keySetID = mKeySets.keyAt(keySetIndex);
+            Set<Long> definedKeys = mKeySetMapping.get(keySetID);
+            if (publicKeyIds.containsAll(definedKeys)) {
+                pkg.keySetData.addSigningKeySet(keySetID);
             }
         }
     }
@@ -181,13 +171,7 @@
      * Fetches the stable identifier associated with the given KeySet. Returns
      * {@link #KEYSET_NOT_FOUND} if the KeySet... wasn't found.
      */
-    public long getIdByKeySet(KeySet ks) {
-        synchronized (mLockObject) {
-            return getIdByKeySetLocked(ks);
-        }
-    }
-
-    private long getIdByKeySetLocked(KeySet ks) {
+    private long getIdByKeySetLPr(KeySet ks) {
         for (int keySetIndex = 0; keySetIndex < mKeySets.size(); keySetIndex++) {
             KeySet value = mKeySets.valueAt(keySetIndex);
             if (ks.equals(value)) {
@@ -203,10 +187,8 @@
      * Returns {@link #KEYSET_NOT_FOUND} if the identifier doesn't
      * identify a {@link KeySet}.
      */
-    public KeySet getKeySetById(long id) {
-        synchronized (mLockObject) {
-            return mKeySets.get(id);
-        }
+    public KeySet getKeySetByIdLPr(long id) {
+        return mKeySets.get(id);
     }
 
     /**
@@ -215,18 +197,16 @@
      * @throws IllegalArgumentException if the package has no keyset data.
      * @throws NullPointerException if the package is unknown.
      */
-    public KeySet getKeySetByAliasAndPackageName(String packageName, String alias) {
-        synchronized (mLockObject) {
-            PackageSetting p = mPackages.get(packageName);
-            if (p == null) {
-                throw new NullPointerException("Unknown package");
-            }
-            if (p.keySetData == null) {
-                throw new IllegalArgumentException("Package has no keySet data");
-            }
-            long keySetId = p.keySetData.getAliases().get(alias);
-            return mKeySets.get(keySetId);
+    public KeySet getKeySetByAliasAndPackageNameLPr(String packageName, String alias) {
+        PackageSetting p = mPackages.get(packageName);
+        if (p == null) {
+            throw new NullPointerException("Unknown package");
         }
+        if (p.keySetData == null) {
+            throw new IllegalArgumentException("Package has no keySet data");
+        }
+        long keySetId = p.keySetData.getAliases().get(alias);
+        return mKeySets.get(keySetId);
     }
 
     /**
@@ -236,17 +216,15 @@
      * Returns {@code null} if the identifier doesn't
      * identify a {@link KeySet}.
      */
-    public ArraySet<PublicKey> getPublicKeysFromKeySet(long id) {
-        synchronized (mLockObject) {
-            if(mKeySetMapping.get(id) == null) {
-                return null;
-            }
-            ArraySet<PublicKey> mPubKeys = new ArraySet<PublicKey>();
-            for (long pkId : mKeySetMapping.get(id)) {
-                mPubKeys.add(mPublicKeys.get(pkId));
-            }
-            return mPubKeys;
+    public ArraySet<PublicKey> getPublicKeysFromKeySetLPr(long id) {
+        if(mKeySetMapping.get(id) == null) {
+            return null;
         }
+        ArraySet<PublicKey> mPubKeys = new ArraySet<PublicKey>();
+        for (long pkId : mKeySetMapping.get(id)) {
+            mPubKeys.add(mPublicKeys.get(pkId));
+        }
+        return mPubKeys;
     }
 
     /**
@@ -256,21 +234,19 @@
      * @throws IllegalArgumentException if the package has no keyset data.
      * @throws NullPointerException if the package is unknown.
      */
-    public Set<KeySet> getSigningKeySetsByPackageName(String packageName) {
-        synchronized (mLockObject) {
-            Set<KeySet> signingKeySets = new ArraySet<KeySet>();
-            PackageSetting p = mPackages.get(packageName);
-            if (p == null) {
-                throw new NullPointerException("Unknown package");
-            }
-            if (p.keySetData == null || p.keySetData.getSigningKeySets() == null) {
-                throw new IllegalArgumentException("Package has no keySet data");
-            }
-            for (long l : p.keySetData.getSigningKeySets()) {
-                signingKeySets.add(mKeySets.get(l));
-            }
-            return signingKeySets;
+    public Set<KeySet> getSigningKeySetsByPackageNameLPr(String packageName) {
+        Set<KeySet> signingKeySets = new ArraySet<KeySet>();
+        PackageSetting p = mPackages.get(packageName);
+        if (p == null) {
+            throw new NullPointerException("Unknown package");
         }
+        if (p.keySetData == null || p.keySetData.getSigningKeySets() == null) {
+            throw new IllegalArgumentException("Package has no keySet data");
+        }
+        for (long l : p.keySetData.getSigningKeySets()) {
+            signingKeySets.add(mKeySets.get(l));
+        }
+        return signingKeySets;
     }
 
     /**
@@ -280,23 +256,21 @@
      * @throws IllegalArgumentException if the package has no keyset data.
      * @throws NullPointerException if the package is unknown.
      */
-    public ArraySet<KeySet> getUpgradeKeySetsByPackageName(String packageName) {
-        synchronized (mLockObject) {
-            ArraySet<KeySet> upgradeKeySets = new ArraySet<KeySet>();
-            PackageSetting p = mPackages.get(packageName);
-            if (p == null) {
-                throw new NullPointerException("Unknown package");
-            }
-            if (p.keySetData == null) {
-                throw new IllegalArgumentException("Package has no keySet data");
-            }
-            if (p.keySetData.isUsingUpgradeKeySets()) {
-                for (long l : p.keySetData.getUpgradeKeySets()) {
-                    upgradeKeySets.add(mKeySets.get(l));
-                }
-            }
-            return upgradeKeySets;
+    public ArraySet<KeySet> getUpgradeKeySetsByPackageNameLPr(String packageName) {
+        ArraySet<KeySet> upgradeKeySets = new ArraySet<KeySet>();
+        PackageSetting p = mPackages.get(packageName);
+        if (p == null) {
+            throw new NullPointerException("Unknown package");
         }
+        if (p.keySetData == null) {
+            throw new IllegalArgumentException("Package has no keySet data");
+        }
+        if (p.keySetData.isUsingUpgradeKeySets()) {
+            for (long l : p.keySetData.getUpgradeKeySets()) {
+                upgradeKeySets.add(mKeySets.get(l));
+            }
+        }
+        return upgradeKeySets;
     }
 
     /**
@@ -313,19 +287,19 @@
      *
      * Throws if the provided set is {@code null}.
      */
-    private KeySet addKeySetLocked(Set<PublicKey> keys) {
+    private KeySet addKeySetLPw(Set<PublicKey> keys) {
         if (keys == null) {
             throw new NullPointerException("Provided keys cannot be null");
         }
         // add each of the keys in the provided set
-        Set<Long> addedKeyIds = new ArraySet<Long>(keys.size());
+        ArraySet<Long> addedKeyIds = new ArraySet<Long>(keys.size());
         for (PublicKey k : keys) {
-            long id = addPublicKeyLocked(k);
+            long id = addPublicKeyLPw(k);
             addedKeyIds.add(id);
         }
 
         // check to see if the resulting keyset is new
-        long existingKeySetId = getIdFromKeyIdsLocked(addedKeyIds);
+        long existingKeySetId = getIdFromKeyIdsLPr(addedKeyIds);
         if (existingKeySetId != KEYSET_NOT_FOUND) {
             return mKeySets.get(existingKeySetId);
         }
@@ -333,7 +307,7 @@
         // create the KeySet object
         KeySet ks = new KeySet(new Binder());
         // get the first unoccupied slot in mKeySets
-        long id = getFreeKeySetIDLocked();
+        long id = getFreeKeySetIDLPw();
         // add the KeySet object to it
         mKeySets.put(id, ks);
         // add the stable key ids to the mapping
@@ -358,14 +332,14 @@
     /**
      * Adds the given PublicKey to the system, deduping as it goes.
      */
-    private long addPublicKeyLocked(PublicKey key) {
+    private long addPublicKeyLPw(PublicKey key) {
         // check if the public key is new
-        long existingKeyId = getIdForPublicKeyLocked(key);
+        long existingKeyId = getIdForPublicKeyLPr(key);
         if (existingKeyId != PUBLIC_KEY_NOT_FOUND) {
             return existingKeyId;
         }
         // if it's new find the first unoccupied slot in the public keys
-        long id = getFreePublicKeyIdLocked();
+        long id = getFreePublicKeyIdLPw();
         // add the public key to it
         mPublicKeys.put(id, key);
         // return the stable identifier
@@ -377,7 +351,7 @@
      *
      * Returns KEYSET_NOT_FOUND if there isn't one.
      */
-    private long getIdFromKeyIdsLocked(Set<Long> publicKeyIds) {
+    private long getIdFromKeyIdsLPr(Set<Long> publicKeyIds) {
         for (int keyMapIndex = 0; keyMapIndex < mKeySetMapping.size(); keyMapIndex++) {
             Set<Long> value = mKeySetMapping.valueAt(keyMapIndex);
             if (value.equals(publicKeyIds)) {
@@ -390,16 +364,7 @@
     /**
      * Finds the stable identifier for a PublicKey or PUBLIC_KEY_NOT_FOUND.
      */
-    protected long getIdForPublicKey(PublicKey k) {
-        synchronized (mLockObject) {
-            return getIdForPublicKeyLocked(k);
-        }
-    }
-
-    /**
-     * Finds the stable identifier for a PublicKey or PUBLIC_KEY_NOT_FOUND.
-     */
-    private long getIdForPublicKeyLocked(PublicKey k) {
+    private long getIdForPublicKeyLPr(PublicKey k) {
         String encodedPublicKey = new String(k.getEncoded());
         for (int publicKeyIndex = 0; publicKeyIndex < mPublicKeys.size(); publicKeyIndex++) {
             PublicKey value = mPublicKeys.valueAt(publicKeyIndex);
@@ -414,7 +379,7 @@
     /**
      * Gets an unused stable identifier for a KeySet.
      */
-    private long getFreeKeySetIDLocked() {
+    private long getFreeKeySetIDLPw() {
         lastIssuedKeySetId += 1;
         return lastIssuedKeySetId;
     }
@@ -422,72 +387,69 @@
     /**
      * Same as above, but for public keys.
      */
-    private long getFreePublicKeyIdLocked() {
+    private long getFreePublicKeyIdLPw() {
         lastIssuedKeyId += 1;
         return lastIssuedKeyId;
     }
 
-    public void removeAppKeySetData(String packageName) {
-        synchronized (mLockObject) {
-            // Get the package's known keys and KeySets
-            Set<Long> deletableKeySets = getOriginalKeySetsByPackageNameLocked(packageName);
-            Set<Long> deletableKeys = new ArraySet<Long>();
-            Set<Long> knownKeys = null;
-            for (Long ks : deletableKeySets) {
+    public void removeAppKeySetDataLPw(String packageName) {
+        // Get the package's known keys and KeySets
+        ArraySet<Long> deletableKeySets = getOriginalKeySetsByPackageNameLPr(packageName);
+        ArraySet<Long> deletableKeys = new ArraySet<Long>();
+        ArraySet<Long> knownKeys = null;
+        for (Long ks : deletableKeySets) {
+            knownKeys = mKeySetMapping.get(ks);
+            if (knownKeys != null) {
+                deletableKeys.addAll(knownKeys);
+            }
+        }
+
+        // Now remove the keys and KeySets on which any other package relies
+        for (String pkgName : mPackages.keySet()) {
+            if (pkgName.equals(packageName)) {
+                continue;
+            }
+            ArraySet<Long> knownKeySets = getOriginalKeySetsByPackageNameLPr(pkgName);
+            deletableKeySets.removeAll(knownKeySets);
+            knownKeys = new ArraySet<Long>();
+            for (Long ks : knownKeySets) {
                 knownKeys = mKeySetMapping.get(ks);
                 if (knownKeys != null) {
-                    deletableKeys.addAll(knownKeys);
+                    deletableKeys.removeAll(knownKeys);
                 }
             }
-
-            // Now remove the keys and KeySets on which any other package relies
-            for (String pkgName : mPackages.keySet()) {
-                if (pkgName.equals(packageName)) {
-                    continue;
-                }
-                Set<Long> knownKeySets = getOriginalKeySetsByPackageNameLocked(pkgName);
-                deletableKeySets.removeAll(knownKeySets);
-                knownKeys = new ArraySet<Long>();
-                for (Long ks : knownKeySets) {
-                    knownKeys = mKeySetMapping.get(ks);
-                    if (knownKeys != null) {
-                        deletableKeys.removeAll(knownKeys);
-                    }
-                }
-            }
-
-            // The remaining keys and KeySets are not relied on by any other
-            // application and so can be safely deleted.
-            for (Long ks : deletableKeySets) {
-                mKeySets.delete(ks);
-                mKeySetMapping.delete(ks);
-            }
-            for (Long keyId : deletableKeys) {
-                mPublicKeys.delete(keyId);
-            }
-
-            // Now remove the deleted KeySets from each package's signingKeySets
-            for (String pkgName : mPackages.keySet()) {
-                PackageSetting p = mPackages.get(pkgName);
-                for (Long ks : deletableKeySets) {
-                    p.keySetData.removeSigningKeySet(ks);
-                }
-            }
-
-            // Finally, remove all KeySets from the original package
-            PackageSetting p = mPackages.get(packageName);
-            clearPackageKeySetDataLocked(p);
         }
+
+        // The remaining keys and KeySets are not relied on by any other
+        // application and so can be safely deleted.
+        for (Long ks : deletableKeySets) {
+            mKeySets.delete(ks);
+            mKeySetMapping.delete(ks);
+        }
+        for (Long keyId : deletableKeys) {
+            mPublicKeys.delete(keyId);
+        }
+
+        // Now remove the deleted KeySets from each package's signingKeySets
+        for (String pkgName : mPackages.keySet()) {
+            PackageSetting p = mPackages.get(pkgName);
+            for (Long ks : deletableKeySets) {
+                p.keySetData.removeSigningKeySet(ks);
+            }
+        }
+        // Finally, remove all KeySets from the original package
+        PackageSetting p = mPackages.get(packageName);
+        clearPackageKeySetDataLPw(p);
     }
 
-    private void clearPackageKeySetDataLocked(PackageSetting p) {
+    private void clearPackageKeySetDataLPw(PackageSetting p) {
         p.keySetData.removeAllSigningKeySets();
         p.keySetData.removeAllUpgradeKeySets();
         p.keySetData.removeAllDefinedKeySets();
         return;
     }
 
-    private Set<Long> getOriginalKeySetsByPackageNameLocked(String packageName) {
+    private ArraySet<Long> getOriginalKeySetsByPackageNameLPr(String packageName) {
         PackageSetting p = mPackages.get(packageName);
         if (p == null) {
             throw new NullPointerException("Unknown package");
@@ -495,7 +457,7 @@
         if (p.keySetData == null) {
             throw new IllegalArgumentException("Package has no keySet data");
         }
-        Set<Long> knownKeySets = new ArraySet<Long>();
+        ArraySet<Long> knownKeySets = new ArraySet<Long>();
         knownKeySets.add(p.keySetData.getProperSigningKeySet());
         if (p.keySetData.isUsingDefinedKeySets()) {
             for (long ks : p.keySetData.getDefinedKeySets()) {
@@ -509,82 +471,80 @@
         return new String(Base64.encode(k.getEncoded(), 0));
     }
 
-    public void dump(PrintWriter pw, String packageName,
-            PackageManagerService.DumpState dumpState) {
-        synchronized (mLockObject) {
-            boolean printedHeader = false;
-            for (Map.Entry<String, PackageSetting> e : mPackages.entrySet()) {
-                String keySetPackage = e.getKey();
-                if (packageName != null && !packageName.equals(keySetPackage)) {
-                    continue;
+    public void dumpLPr(PrintWriter pw, String packageName,
+                        PackageManagerService.DumpState dumpState) {
+        boolean printedHeader = false;
+        for (Map.Entry<String, PackageSetting> e : mPackages.entrySet()) {
+            String keySetPackage = e.getKey();
+            if (packageName != null && !packageName.equals(keySetPackage)) {
+                continue;
+            }
+            if (!printedHeader) {
+                if (dumpState.onTitlePrinted())
+                    pw.println();
+                pw.println("Key Set Manager:");
+                printedHeader = true;
+            }
+            PackageSetting pkg = e.getValue();
+            pw.print("  ["); pw.print(keySetPackage); pw.println("]");
+            if (pkg.keySetData != null) {
+                boolean printedLabel = false;
+                for (Map.Entry<String, Long> entry : pkg.keySetData.getAliases().entrySet()) {
+                    if (!printedLabel) {
+                        pw.print("      KeySets Aliases: ");
+                        printedLabel = true;
+                    } else {
+                        pw.print(", ");
+                    }
+                    pw.print(entry.getKey());
+                    pw.print('=');
+                    pw.print(Long.toString(entry.getValue()));
                 }
-                if (!printedHeader) {
-                    if (dumpState.onTitlePrinted())
-                        pw.println();
-                    pw.println("Key Set Manager:");
-                    printedHeader = true;
+                if (printedLabel) {
+                    pw.println("");
                 }
-                PackageSetting pkg = e.getValue();
-                pw.print("  ["); pw.print(keySetPackage); pw.println("]");
-                if (pkg.keySetData != null) {
-                    boolean printedLabel = false;
-                    for (Map.Entry<String, Long> entry : pkg.keySetData.getAliases().entrySet()) {
+                printedLabel = false;
+                if (pkg.keySetData.isUsingDefinedKeySets()) {
+                    for (long keySetId : pkg.keySetData.getDefinedKeySets()) {
                         if (!printedLabel) {
-                            pw.print("      KeySets Aliases: ");
-                            printedLabel = true;
-                        } else {
-                            pw.print(", ");
-                        }
-                        pw.print(entry.getKey());
-                        pw.print('=');
-                        pw.print(Long.toString(entry.getValue()));
-                    }
-                    if (printedLabel) {
-                        pw.println("");
-                    }
-                    printedLabel = false;
-                    if (pkg.keySetData.isUsingDefinedKeySets()) {
-                        for (long keySetId : pkg.keySetData.getDefinedKeySets()) {
-                            if (!printedLabel) {
-                                pw.print("      Defined KeySets: ");
-                                printedLabel = true;
-                            } else {
-                                pw.print(", ");
-                            }
-                            pw.print(Long.toString(keySetId));
-                        }
-                    }
-                    if (printedLabel) {
-                        pw.println("");
-                    }
-                    printedLabel = false;
-                    for (long keySetId : pkg.keySetData.getSigningKeySets()) {
-                        if (!printedLabel) {
-                            pw.print("      Signing KeySets: ");
+                            pw.print("      Defined KeySets: ");
                             printedLabel = true;
                         } else {
                             pw.print(", ");
                         }
                         pw.print(Long.toString(keySetId));
                     }
-                    if (printedLabel) {
-                        pw.println("");
+                }
+                if (printedLabel) {
+                    pw.println("");
+                }
+                printedLabel = false;
+                for (long keySetId : pkg.keySetData.getSigningKeySets()) {
+                    if (!printedLabel) {
+                        pw.print("      Signing KeySets: ");
+                        printedLabel = true;
+                    } else {
+                        pw.print(", ");
                     }
-                    printedLabel = false;
-                    if (pkg.keySetData.isUsingUpgradeKeySets()) {
-                        for (long keySetId : pkg.keySetData.getUpgradeKeySets()) {
-                            if (!printedLabel) {
-                                pw.print("      Upgrade KeySets: ");
-                                printedLabel = true;
-                            } else {
-                                pw.print(", ");
-                            }
-                            pw.print(Long.toString(keySetId));
+                    pw.print(Long.toString(keySetId));
+                }
+                if (printedLabel) {
+                    pw.println("");
+                }
+                printedLabel = false;
+                if (pkg.keySetData.isUsingUpgradeKeySets()) {
+                    for (long keySetId : pkg.keySetData.getUpgradeKeySets()) {
+                        if (!printedLabel) {
+                            pw.print("      Upgrade KeySets: ");
+                            printedLabel = true;
+                        } else {
+                            pw.print(", ");
                         }
+                        pw.print(Long.toString(keySetId));
                     }
-                    if (printedLabel) {
-                        pw.println("");
-                    }
+                }
+                if (printedLabel) {
+                    pw.println("");
                 }
             }
         }
@@ -651,7 +611,7 @@
             // The KeySet information read previously from packages.xml is invalid.
             // Destroy it all.
             for (PackageSetting p : mPackages.values()) {
-                clearPackageKeySetDataLocked(p);
+                clearPackageKeySetDataLPw(p);
             }
             return;
         }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 682a38d..5f65a5e 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -24,12 +24,15 @@
 import android.content.Context;
 import android.content.pm.IPackageDeleteObserver;
 import android.content.pm.IPackageInstaller;
+import android.content.pm.IPackageInstallerObserver;
 import android.content.pm.IPackageInstallerSession;
-import android.content.pm.PackageInstallerParams;
+import android.content.pm.InstallSessionInfo;
+import android.content.pm.InstallSessionParams;
 import android.os.Binder;
 import android.os.FileUtils;
 import android.os.HandlerThread;
 import android.os.Process;
+import android.os.RemoteException;
 import android.os.SELinux;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -47,6 +50,8 @@
 import java.io.File;
 import java.io.FilenameFilter;
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
 
 public class PackageInstallerService extends IPackageInstaller.Stub {
     private static final String TAG = "PackageInstaller";
@@ -138,7 +143,7 @@
     }
 
     @Override
-    public int createSession(String installerPackageName, PackageInstallerParams params,
+    public int createSession(String installerPackageName, InstallSessionParams params,
             int userId) {
         final int callingUid = Binder.getCallingUid();
         mPm.enforceCrossUserPermission(callingUid, userId, false, TAG);
@@ -217,29 +222,25 @@
     }
 
     @Override
-    public int[] getSessions(String installerPackageName, int userId) {
-        final int callingUid = Binder.getCallingUid();
-        mPm.enforceCrossUserPermission(callingUid, userId, false, TAG);
-        mAppOps.checkPackage(callingUid, installerPackageName);
+    public List<InstallSessionInfo> getSessions(int userId) {
+        mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, false, TAG);
 
-        int[] matching = new int[0];
+        final List<InstallSessionInfo> result = new ArrayList<>();
         synchronized (mSessions) {
             for (int i = 0; i < mSessions.size(); i++) {
-                final int key = mSessions.keyAt(i);
                 final PackageInstallerSession session = mSessions.valueAt(i);
-                if (session.userId == userId
-                        && session.installerPackageName.equals(installerPackageName)) {
-                    matching = ArrayUtils.appendInt(matching, key);
+                if (session.userId == userId) {
+                    result.add(session.generateInfo());
                 }
             }
         }
-        return matching;
+        return result;
     }
 
     @Override
-    public void uninstall(String basePackageName, int flags, IPackageDeleteObserver observer,
+    public void uninstall(String packageName, int flags, IPackageDeleteObserver observer,
             int userId) {
-        mPm.deletePackageAsUser(basePackageName, observer, userId, flags);
+        mPm.deletePackageAsUser(packageName, observer, userId, flags);
     }
 
     @Override
@@ -249,6 +250,16 @@
         throw new UnsupportedOperationException();
     }
 
+    @Override
+    public void registerObserver(IPackageInstallerObserver observer, int userId) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void unregisterObserver(IPackageInstallerObserver observer, int userId) {
+        throw new UnsupportedOperationException();
+    }
+
     class Callback {
         public void onProgressChanged(PackageInstallerSession session) {
             // TODO: notify listeners
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 3448803..6a7a0b2 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -26,7 +26,8 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageInstallObserver2;
 import android.content.pm.IPackageInstallerSession;
-import android.content.pm.PackageInstallerParams;
+import android.content.pm.InstallSessionInfo;
+import android.content.pm.InstallSessionParams;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageParser;
 import android.content.pm.PackageParser.ApkLite;
@@ -75,7 +76,7 @@
     public final String installerPackageName;
     /** UID not persisted */
     public final int installerUid;
-    public final PackageInstallerParams params;
+    public final InstallSessionParams params;
     public final long createdMillis;
     public final File sessionStageDir;
 
@@ -95,7 +96,8 @@
                     Slog.e(TAG, "Install failed: " + e);
                     destroy();
                     try {
-                        mRemoteObserver.packageInstalled(mPackageName, null, e.error);
+                        mRemoteObserver.packageInstalled(mPackageName, null, e.error,
+                                e.getMessage());
                     } catch (RemoteException ignored) {
                     }
                 }
@@ -123,7 +125,7 @@
 
     public PackageInstallerSession(PackageInstallerService.Callback callback,
             PackageManagerService pm, int sessionId, int userId, String installerPackageName,
-            int installerUid, PackageInstallerParams params, long createdMillis, File sessionStageDir,
+            int installerUid, InstallSessionParams params, long createdMillis, File sessionStageDir,
             Looper looper) {
         mCallback = callback;
         mPm = pm;
@@ -152,6 +154,21 @@
         }
     }
 
+    public InstallSessionInfo generateInfo() {
+        final InstallSessionInfo info = new InstallSessionInfo();
+
+        info.sessionId = sessionId;
+        info.installerPackageName = installerPackageName;
+        info.progress = mProgress;
+
+        info.fullInstall = params.fullInstall;
+        info.packageName = params.packageName;
+        info.icon = params.icon;
+        info.title = params.title;
+
+        return info;
+    }
+
     @Override
     public void updateProgress(int progress) {
         mProgress = progress;
@@ -264,15 +281,15 @@
         final IPackageInstallObserver2 remoteObserver = mRemoteObserver;
         final IPackageInstallObserver2 localObserver = new IPackageInstallObserver2.Stub() {
             @Override
-            public void packageInstalled(String basePackageName, Bundle extras, int returnCode)
-                    throws RemoteException {
+            public void packageInstalled(String basePackageName, Bundle extras, int returnCode,
+                    String msg) throws RemoteException {
                 destroy();
-                remoteObserver.packageInstalled(basePackageName, extras, returnCode);
+                remoteObserver.packageInstalled(basePackageName, extras, returnCode, msg);
             }
         };
 
-        mPm.installStage(mPackageName, this.sessionStageDir, localObserver, params, installerPackageName,
-                installerUid, new UserHandle(userId));
+        mPm.installStage(mPackageName, this.sessionStageDir, localObserver, params,
+                installerPackageName, installerUid, new UserHandle(userId));
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 1accfba..9edadb5 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -91,12 +91,12 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.IPackageMoveObserver;
 import android.content.pm.IPackageStatsObserver;
+import android.content.pm.InstallSessionParams;
 import android.content.pm.InstrumentationInfo;
 import android.content.pm.ManifestDigest;
 import android.content.pm.PackageCleanItem;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInfoLite;
-import android.content.pm.PackageInstallerParams;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageParser.ActivityIntentInfo;
 import android.content.pm.PackageParser.PackageLite;
@@ -1080,7 +1080,7 @@
                         if (args.observer != null) {
                             try {
                                 Bundle extras = extrasForInstallResult(res);
-                                args.observer.packageInstalled(res.name, extras, res.returnCode);
+                                args.observer.packageInstalled(res.name, extras, res.returnCode, null);
                             } catch (RemoteException e) {
                                 Slog.i(TAG, "Observer no longer exists.");
                             }
@@ -2815,7 +2815,9 @@
             // Migrate the old signatures to the new scheme.
             existingSigs.assignSignatures(scannedPkg.mSignatures);
             // The new KeySets will be re-added later in the scanning process.
-            mSettings.mKeySetManagerService.removeAppKeySetData(scannedPkg.packageName);
+            synchronized (mPackages) {
+                mSettings.mKeySetManagerService.removeAppKeySetDataLPw(scannedPkg.packageName);
+            }
             return PackageManager.SIGNATURE_MATCH;
         }
         return PackageManager.SIGNATURE_NO_MATCH;
@@ -4145,7 +4147,9 @@
                 // if the package appears to be unchanged.
                 pkg.mSignatures = ps.signatures.mSignatures;
                 KeySetManagerService ksms = mSettings.mKeySetManagerService;
-                pkg.mSigningKeys = ksms.getPublicKeysFromKeySet(mSigningKeySetId);
+                synchronized (mPackages) {
+                    pkg.mSigningKeys = ksms.getPublicKeysFromKeySetLPr(mSigningKeySetId);
+                }
                 return true;
             }
 
@@ -5704,19 +5708,19 @@
             KeySetManagerService ksms = mSettings.mKeySetManagerService;
             try {
                 // Old KeySetData no longer valid.
-                ksms.removeAppKeySetData(pkg.packageName);
-                ksms.addSigningKeySetToPackage(pkg.packageName, pkg.mSigningKeys);
+                ksms.removeAppKeySetDataLPw(pkg.packageName);
+                ksms.addSigningKeySetToPackageLPw(pkg.packageName, pkg.mSigningKeys);
                 if (pkg.mKeySetMapping != null) {
                     for (Map.Entry<String, ArraySet<PublicKey>> entry :
                             pkg.mKeySetMapping.entrySet()) {
                         if (entry.getValue() != null) {
-                            ksms.addDefinedKeySetToPackage(pkg.packageName,
+                            ksms.addDefinedKeySetToPackageLPw(pkg.packageName,
                                                           entry.getValue(), entry.getKey());
                         }
                     }
                     if (pkg.mUpgradeKeySets != null) {
                         for (String upgradeAlias : pkg.mUpgradeKeySets) {
-                            ksms.addUpgradeKeySetToPackage(pkg.packageName, upgradeAlias);
+                            ksms.addUpgradeKeySetToPackageLPw(pkg.packageName, upgradeAlias);
                         }
                     }
                 }
@@ -7746,7 +7750,7 @@
         if (isUserRestricted(UserHandle.getUserId(uid), UserManager.DISALLOW_INSTALL_APPS)) {
             try {
                 if (observer != null) {
-                    observer.packageInstalled("", null, INSTALL_FAILED_USER_RESTRICTED);
+                    observer.packageInstalled("", null, INSTALL_FAILED_USER_RESTRICTED, null);
                 }
             } catch (RemoteException re) {
             }
@@ -7779,7 +7783,7 @@
     }
 
     void installStage(String packageName, File stageDir, IPackageInstallObserver2 observer,
-            PackageInstallerParams params, String installerPackageName, int installerUid,
+            InstallSessionParams params, String installerPackageName, int installerUid,
             UserHandle user) {
         final VerificationParams verifParams = new VerificationParams(null, params.originatingUri,
                 params.referrerUri, installerUid, null);
@@ -9906,14 +9910,9 @@
         // required keys.
         long[] upgradeKeySets = oldPS.keySetData.getUpgradeKeySets();
         KeySetManagerService ksms = mSettings.mKeySetManagerService;
-        Set<Long> newSigningKeyIds = new ArraySet<Long>();
-        for (PublicKey pk : newPkg.mSigningKeys) {
-            newSigningKeyIds.add(ksms.getIdForPublicKey(pk));
-        }
-        //remove PUBLIC_KEY_NOT_FOUND, although not necessary
-        newSigningKeyIds.remove(ksms.PUBLIC_KEY_NOT_FOUND);
         for (int i = 0; i < upgradeKeySets.length; i++) {
-            if (newSigningKeyIds.containsAll(ksms.mKeySetMapping.get(upgradeKeySets[i]))) {
+            Set<PublicKey> upgradeSet = ksms.getPublicKeysFromKeySetLPr(upgradeKeySets[i]);
+            if (newPkg.mSigningKeys.containsAll(upgradeSet)) {
                 return true;
             }
         }
@@ -10707,7 +10706,7 @@
             if (deletedPs != null) {
                 if ((flags&PackageManager.DELETE_KEEP_DATA) == 0) {
                     if (outInfo != null) {
-                        mSettings.mKeySetManagerService.removeAppKeySetData(packageName);
+                        mSettings.mKeySetManagerService.removeAppKeySetDataLPw(packageName);
                         outInfo.removedAppId = mSettings.removePackageLPw(packageName);
                     }
                     if (deletedPs != null) {
@@ -12383,7 +12382,7 @@
             }
 
             if (!checkin && dumpState.isDumping(DumpState.DUMP_KEYSETS)) {
-                mSettings.mKeySetManagerService.dump(pw, packageName, dumpState);
+                mSettings.mKeySetManagerService.dumpLPr(pw, packageName, dumpState);
             }
 
             if (dumpState.isDumping(DumpState.DUMP_PACKAGES)) {