Add encryption parameters to package installation

Change-Id: Ic9f8ab9f8110f08bb3c00725cfce5b8ee7b766f3
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index e19ad66..f9ff861 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -16,9 +16,12 @@
 
 package com.android.commands.pm;
 
+import com.android.internal.content.PackageHelper;
+
 import android.app.ActivityManagerNative;
 import android.content.ComponentName;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.ContainerEncryptionParams;
 import android.content.pm.FeatureInfo;
 import android.content.pm.IPackageDataObserver;
 import android.content.pm.IPackageDeleteObserver;
@@ -40,17 +43,20 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 
-import com.android.internal.content.PackageHelper;
-
 import java.io.File;
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
+import java.security.InvalidAlgorithmParameterException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 import java.util.WeakHashMap;
 
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
 public final class Pm {
     IPackageManager mPm;
 
@@ -763,6 +769,15 @@
         String installerPackageName = null;
 
         String opt;
+
+        String algo = null;
+        byte[] iv = null;
+        byte[] key = null;
+
+        String macAlgo = null;
+        byte[] macKey = null;
+        byte[] tag = null;
+
         while ((opt=nextOption()) != null) {
             if (opt.equals("-l")) {
                 installFlags |= PackageManager.INSTALL_FORWARD_LOCK;
@@ -783,6 +798,48 @@
             } else if (opt.equals("-f")) {
                 // Override if -s option is specified.
                 installFlags |= PackageManager.INSTALL_INTERNAL;
+            } else if (opt.equals("--algo")) {
+                algo = nextOptionData();
+                if (algo == null) {
+                    System.err.println("Error: must supply argument for --algo");
+                    showUsage();
+                    return;
+                }
+            } else if (opt.equals("--iv")) {
+                iv = hexToBytes(nextOptionData());
+                if (iv == null) {
+                    System.err.println("Error: must supply argument for --iv");
+                    showUsage();
+                    return;
+                }
+            } else if (opt.equals("--key")) {
+                key = hexToBytes(nextOptionData());
+                if (key == null) {
+                    System.err.println("Error: must supply argument for --key");
+                    showUsage();
+                    return;
+                }
+            } else if (opt.equals("--macalgo")) {
+                macAlgo = nextOptionData();
+                if (macAlgo == null) {
+                    System.err.println("Error: must supply argument for --macalgo");
+                    showUsage();
+                    return;
+                }
+            } else if (opt.equals("--mackey")) {
+                macKey = hexToBytes(nextOptionData());
+                if (macKey == null) {
+                    System.err.println("Error: must supply argument for --mackey");
+                    showUsage();
+                    return;
+                }
+            } else if (opt.equals("--tag")) {
+                tag = hexToBytes(nextOptionData());
+                if (tag == null) {
+                    System.err.println("Error: must supply argument for --tag");
+                    showUsage();
+                    return;
+                }
             } else {
                 System.err.println("Error: Unknown option: " + opt);
                 showUsage();
@@ -790,6 +847,44 @@
             }
         }
 
+        final ContainerEncryptionParams encryptionParams;
+        if (algo != null || iv != null || key != null || macAlgo != null || macKey != null
+                || tag != null) {
+            if (algo == null || iv == null || key == null) {
+                System.err.println("Error: all of --algo, --iv, and --key must be specified");
+                showUsage();
+                return;
+            }
+
+            if (macAlgo != null || macKey != null || tag != null) {
+                if (macAlgo == null || macKey == null || tag == null) {
+                    System.err.println("Error: all of --macalgo, --mackey, and --tag must "
+                            + "be specified");
+                    showUsage();
+                    return;
+                }
+            }
+
+            try {
+                final SecretKey encKey = new SecretKeySpec(key, "RAW");
+
+                final SecretKey macSecretKey;
+                if (macKey == null || macKey.length == 0) {
+                    macSecretKey = null;
+                } else {
+                    macSecretKey = new SecretKeySpec(macKey, "RAW");
+                }
+
+                encryptionParams = new ContainerEncryptionParams(algo, new IvParameterSpec(iv),
+                        encKey, macAlgo, null, macSecretKey, tag, -1, -1, -1);
+            } catch (InvalidAlgorithmParameterException e) {
+                e.printStackTrace();
+                return;
+            }
+        } else {
+            encryptionParams = null;
+        }
+
         final Uri apkURI;
         final Uri verificationURI;
 
@@ -816,7 +911,7 @@
         PackageInstallObserver obs = new PackageInstallObserver();
         try {
             mPm.installPackageWithVerification(apkURI, obs, installFlags, installerPackageName,
-                    verificationURI, null);
+                    verificationURI, null, encryptionParams);
 
             synchronized (obs) {
                 while (!obs.finished) {
@@ -839,6 +934,37 @@
         }
     }
 
+    /**
+     * Convert a string containing hex-encoded bytes to a byte array.
+     *
+     * @param input String containing hex-encoded bytes
+     * @return input as an array of bytes
+     */
+    private byte[] hexToBytes(String input) {
+        if (input == null) {
+            return null;
+        }
+
+        final int inputLength = input.length();
+        if ((inputLength % 2) != 0) {
+            System.err.print("Invalid length; must be multiple of 2");
+            return null;
+        }
+
+        final int byteLength = inputLength / 2;
+        final byte[] output = new byte[byteLength];
+
+        int inputIndex = 0;
+        int byteIndex = 0;
+        while (inputIndex < inputLength) {
+            output[byteIndex++] = (byte) Integer.parseInt(
+                    input.substring(inputIndex, inputIndex + 2), 16);
+            inputIndex += 2;
+        }
+
+        return output;
+    }
+
     public void runCreateUser() {
         // Need to be run as root
         if (Process.myUid() != ROOT_UID) {
@@ -1236,7 +1362,8 @@
         System.err.println("       pm list libraries");
         System.err.println("       pm list users");
         System.err.println("       pm path PACKAGE");
-        System.err.println("       pm install [-l] [-r] [-t] [-i INSTALLER_PACKAGE_NAME] [-s] [-f] PATH");
+        System.err.println("       pm install [-l] [-r] [-t] [-i INSTALLER_PACKAGE_NAME] [-s] [-f]");
+        System.err.println("                  [--algo <algorithm name> --key <key-in-hex> --iv <IV-in-hex>] PATH");
         System.err.println("       pm uninstall [-k] PACKAGE");
         System.err.println("       pm clear PACKAGE");
         System.err.println("       pm enable [--user USER_ID] PACKAGE_OR_COMPONENT");
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 0510de1..191a696 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -24,6 +24,7 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ComponentInfo;
+import android.content.pm.ContainerEncryptionParams;
 import android.content.pm.FeatureInfo;
 import android.content.pm.IPackageDataObserver;
 import android.content.pm.IPackageDeleteObserver;
@@ -973,10 +974,10 @@
     @Override
     public void installPackageWithVerification(Uri packageURI, IPackageInstallObserver observer,
             int flags, String installerPackageName, Uri verificationURI,
-            ManifestDigest manifestDigest) {
+            ManifestDigest manifestDigest, ContainerEncryptionParams encryptionParams) {
         try {
             mPM.installPackageWithVerification(packageURI, observer, flags, installerPackageName,
-                    verificationURI, manifestDigest);
+                    verificationURI, manifestDigest, encryptionParams);
         } catch (RemoteException e) {
             // Should never happen!
         }
diff --git a/core/java/android/content/pm/ContainerEncryptionParams.aidl b/core/java/android/content/pm/ContainerEncryptionParams.aidl
new file mode 100644
index 0000000..c13d946
--- /dev/null
+++ b/core/java/android/content/pm/ContainerEncryptionParams.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2012, 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 ContainerEncryptionParams;
diff --git a/core/java/android/content/pm/ContainerEncryptionParams.java b/core/java/android/content/pm/ContainerEncryptionParams.java
new file mode 100644
index 0000000..5b1440d
--- /dev/null
+++ b/core/java/android/content/pm/ContainerEncryptionParams.java
@@ -0,0 +1,378 @@
+/*
+ * Copyright (C) 2012 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.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.spec.AlgorithmParameterSpec;
+import java.util.Arrays;
+
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+
+/**
+ * Represents encryption parameters used to read a container.
+ *
+ * @hide
+ */
+public class ContainerEncryptionParams implements Parcelable {
+    protected static final String TAG = "ContainerEncryptionParams";
+
+    /** What we print out first when toString() is called. */
+    private static final String TO_STRING_PREFIX = "ContainerEncryptionParams{";
+
+    /**
+     * Parameter type for parceling that indicates the next parameters are
+     * IvParameters.
+     */
+    private static final int ENC_PARAMS_IV_PARAMETERS = 1;
+
+    /** Parameter type for paceling that indicates there are no MAC parameters. */
+    private static final int MAC_PARAMS_NONE = 1;
+
+    /** The encryption algorithm used. */
+    private final String mEncryptionAlgorithm;
+
+    /** The parameter spec to be used for encryption. */
+    private final IvParameterSpec mEncryptionSpec;
+
+    /** Secret key to be used for decryption. */
+    private final SecretKey mEncryptionKey;
+
+    /** Algorithm name for the MAC to be used. */
+    private final String mMacAlgorithm;
+
+    /** The parameter spec to be used for the MAC tag authentication. */
+    private final AlgorithmParameterSpec mMacSpec;
+
+    /** Secret key to be used for MAC tag authentication. */
+    private final SecretKey mMacKey;
+
+    /** MAC tag authenticating the data in the container. */
+    private final byte[] mMacTag;
+
+    /** Offset into file where authenticated (e.g., MAC protected) data begins. */
+    private final int mAuthenticatedDataStart;
+
+    /** Offset into file where encrypted data begins. */
+    private final int mEncryptedDataStart;
+
+    /**
+     * Offset into file for the end of encrypted data (and, by extension,
+     * authenticated data) in file.
+     */
+    private final int mDataEnd;
+
+    public ContainerEncryptionParams(String encryptionAlgorithm,
+            AlgorithmParameterSpec encryptionSpec, SecretKey encryptionKey)
+            throws InvalidAlgorithmParameterException {
+        this(encryptionAlgorithm, encryptionSpec, encryptionKey, null, null, null, null, -1, -1,
+                -1);
+    }
+
+    /**
+     * Creates container encryption specifications for installing from encrypted
+     * containers.
+     *
+     * @param encryptionAlgorithm encryption algorithm to use; format matches
+     *            JCE
+     * @param encryptionSpec algorithm parameter specification
+     * @param encryptionKey key used for decryption
+     * @param macAlgorithm MAC algorithm to use; format matches JCE
+     * @param macSpec algorithm parameters specification, may be {@code null}
+     * @param macKey key used for authentication (i.e., for the MAC tag)
+     * @param authenticatedDataStart offset of start of authenticated data in
+     *            stream
+     * @param encryptedDataStart offset of start of encrypted data in stream
+     * @param dataEnd offset of the end of both the authenticated and encrypted
+     *            data
+     * @throws InvalidAlgorithmParameterException
+     */
+    public ContainerEncryptionParams(String encryptionAlgorithm,
+            AlgorithmParameterSpec encryptionSpec, SecretKey encryptionKey, String macAlgorithm,
+            AlgorithmParameterSpec macSpec, SecretKey macKey, byte[] macTag,
+            int authenticatedDataStart, int encryptedDataStart, int dataEnd)
+            throws InvalidAlgorithmParameterException {
+        if (TextUtils.isEmpty(encryptionAlgorithm)) {
+            throw new NullPointerException("algorithm == null");
+        } else if (encryptionSpec == null) {
+            throw new NullPointerException("encryptionSpec == null");
+        } else if (encryptionKey == null) {
+            throw new NullPointerException("encryptionKey == null");
+        }
+
+        if (!TextUtils.isEmpty(macAlgorithm)) {
+            if (macKey == null) {
+                throw new NullPointerException("macKey == null");
+            }
+        }
+
+        if (!(encryptionSpec instanceof IvParameterSpec)) {
+            throw new InvalidAlgorithmParameterException(
+                    "Unknown parameter spec class; must be IvParameters");
+        }
+
+        mEncryptionAlgorithm = encryptionAlgorithm;
+        mEncryptionSpec = (IvParameterSpec) encryptionSpec;
+        mEncryptionKey = encryptionKey;
+
+        mMacAlgorithm = macAlgorithm;
+        mMacSpec = macSpec;
+        mMacKey = macKey;
+        mMacTag = macTag;
+
+        mAuthenticatedDataStart = authenticatedDataStart;
+        mEncryptedDataStart = encryptedDataStart;
+        mDataEnd = dataEnd;
+    }
+
+    public String getEncryptionAlgorithm() {
+        return mEncryptionAlgorithm;
+    }
+
+    public AlgorithmParameterSpec getEncryptionSpec() {
+        return mEncryptionSpec;
+    }
+
+    public SecretKey getEncryptionKey() {
+        return mEncryptionKey;
+    }
+
+    public String getMacAlgorithm() {
+        return mMacAlgorithm;
+    }
+
+    public AlgorithmParameterSpec getMacSpec() {
+        return mMacSpec;
+    }
+
+    public SecretKey getMacKey() {
+        return mMacKey;
+    }
+
+    public byte[] getMacTag() {
+        return mMacTag;
+    }
+
+    public int getAuthenticatedDataStart() {
+        return mAuthenticatedDataStart;
+    }
+
+    public int getEncryptedDataStart() {
+        return mEncryptedDataStart;
+    }
+
+    public int getDataEnd() {
+        return mDataEnd;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+
+        if (!(o instanceof ContainerEncryptionParams)) {
+            return false;
+        }
+
+        final ContainerEncryptionParams other = (ContainerEncryptionParams) o;
+
+        // Primitive comparison
+        if ((mAuthenticatedDataStart != other.mAuthenticatedDataStart)
+                || (mEncryptedDataStart != other.mEncryptedDataStart)
+                || (mDataEnd != other.mDataEnd)) {
+            return false;
+        }
+
+        // String comparison
+        if (!mEncryptionAlgorithm.equals(other.mEncryptionAlgorithm)
+                || !mMacAlgorithm.equals(other.mMacAlgorithm)) {
+            return false;
+        }
+
+        // Object comparison
+        if (!isSecretKeyEqual(mEncryptionKey, other.mEncryptionKey)
+                || !isSecretKeyEqual(mMacKey, other.mMacKey)) {
+            return false;
+        }
+
+        if (!Arrays.equals(mEncryptionSpec.getIV(), other.mEncryptionSpec.getIV())
+                || !Arrays.equals(mMacTag, other.mMacTag) || (mMacSpec != other.mMacSpec)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    private static final boolean isSecretKeyEqual(SecretKey key1, SecretKey key2) {
+        final String keyFormat = key1.getFormat();
+        final String otherKeyFormat = key2.getFormat();
+
+        if (keyFormat == null) {
+            if (keyFormat != otherKeyFormat) {
+                return false;
+            }
+
+            if (key1.getEncoded() != key2.getEncoded()) {
+                return false;
+            }
+        } else {
+            if (!keyFormat.equals(key2.getFormat())) {
+                return false;
+            }
+
+            if (!Arrays.equals(key1.getEncoded(), key2.getEncoded())) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = 3;
+
+        hash += 5 * mEncryptionAlgorithm.hashCode();
+        hash += 7 * Arrays.hashCode(mEncryptionSpec.getIV());
+        hash += 11 * mEncryptionKey.hashCode();
+        hash += 13 * mMacAlgorithm.hashCode();
+        hash += 17 * mMacKey.hashCode();
+        hash += 19 * Arrays.hashCode(mMacTag);
+        hash += 23 * mAuthenticatedDataStart;
+        hash += 29 * mEncryptedDataStart;
+        hash += 31 * mDataEnd;
+
+        return hash;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder(TO_STRING_PREFIX);
+
+        sb.append("mEncryptionAlgorithm=\"");
+        sb.append(mEncryptionAlgorithm);
+        sb.append("\",");
+        sb.append("mEncryptionSpec=");
+        sb.append(mEncryptionSpec.toString());
+        sb.append("mEncryptionKey=");
+        sb.append(mEncryptionKey.toString());
+
+        sb.append("mMacAlgorithm=\"");
+        sb.append(mMacAlgorithm);
+        sb.append("\",");
+        sb.append("mMacSpec=");
+        sb.append(mMacSpec.toString());
+        sb.append("mMacKey=");
+        sb.append(mMacKey.toString());
+
+        sb.append(",mAuthenticatedDataStart=");
+        sb.append(mAuthenticatedDataStart);
+        sb.append(",mEncryptedDataStart=");
+        sb.append(mEncryptedDataStart);
+        sb.append(",mDataEnd=");
+        sb.append(mDataEnd);
+        sb.append('}');
+
+        return sb.toString();
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mEncryptionAlgorithm);
+        dest.writeInt(ENC_PARAMS_IV_PARAMETERS);
+        dest.writeByteArray(mEncryptionSpec.getIV());
+        dest.writeSerializable(mEncryptionKey);
+
+        dest.writeString(mMacAlgorithm);
+        dest.writeInt(MAC_PARAMS_NONE);
+        dest.writeByteArray(new byte[0]);
+        dest.writeSerializable(mMacKey);
+
+        dest.writeByteArray(mMacTag);
+
+        dest.writeInt(mAuthenticatedDataStart);
+        dest.writeInt(mEncryptedDataStart);
+        dest.writeInt(mDataEnd);
+    }
+
+    private ContainerEncryptionParams(Parcel source) throws InvalidAlgorithmParameterException {
+        mEncryptionAlgorithm = source.readString();
+        final int encParamType = source.readInt();
+        final byte[] encParamsEncoded = source.createByteArray();
+        mEncryptionKey = (SecretKey) source.readSerializable();
+
+        mMacAlgorithm = source.readString();
+        final int macParamType = source.readInt();
+        source.createByteArray(); // byte[] macParamsEncoded
+        mMacKey = (SecretKey) source.readSerializable();
+
+        mMacTag = source.createByteArray();
+
+        mAuthenticatedDataStart = source.readInt();
+        mEncryptedDataStart = source.readInt();
+        mDataEnd = source.readInt();
+
+        switch (encParamType) {
+            case ENC_PARAMS_IV_PARAMETERS:
+                mEncryptionSpec = new IvParameterSpec(encParamsEncoded);
+                break;
+            default:
+                throw new InvalidAlgorithmParameterException("Unknown parameter type "
+                        + encParamType);
+        }
+
+        switch (macParamType) {
+            case MAC_PARAMS_NONE:
+                mMacSpec = null;
+                break;
+            default:
+                throw new InvalidAlgorithmParameterException("Unknown parameter type "
+                        + macParamType);
+        }
+
+        if (mEncryptionKey == null) {
+            throw new NullPointerException("encryptionKey == null");
+        }
+    }
+
+    public static final Parcelable.Creator<ContainerEncryptionParams> CREATOR =
+            new Parcelable.Creator<ContainerEncryptionParams>() {
+        public ContainerEncryptionParams createFromParcel(Parcel source) {
+            try {
+                return new ContainerEncryptionParams(source);
+            } catch (InvalidAlgorithmParameterException e) {
+                Slog.e(TAG, "Invalid algorithm parameters specified", e);
+                return null;
+            }
+        }
+
+        public ContainerEncryptionParams[] newArray(int size) {
+            return new ContainerEncryptionParams[size];
+        }
+    };
+}
\ No newline at end of file
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 9b8454a..70c0c48 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -22,6 +22,7 @@
 import android.content.IntentFilter;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.ContainerEncryptionParams;
 import android.content.pm.FeatureInfo;
 import android.content.pm.IPackageInstallObserver;
 import android.content.pm.IPackageDeleteObserver;
@@ -362,7 +363,7 @@
 
     void installPackageWithVerification(in Uri packageURI, in IPackageInstallObserver observer,
             int flags, in String installerPackageName, in Uri verificationURI,
-            in ManifestDigest manifestDigest);
+            in ManifestDigest manifestDigest, in ContainerEncryptionParams encryptionParams);
 
     void verifyPendingInstall(int id, int verificationCode);
 
diff --git a/core/java/android/content/pm/LimitedLengthInputStream.java b/core/java/android/content/pm/LimitedLengthInputStream.java
new file mode 100644
index 0000000..25a490f
--- /dev/null
+++ b/core/java/android/content/pm/LimitedLengthInputStream.java
@@ -0,0 +1,82 @@
+package android.content.pm;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * A class that limits the amount of data that is read from an InputStream. When
+ * the specified length is reached, the stream returns an EOF even if the
+ * underlying stream still has more data.
+ *
+ * @hide
+ */
+public class LimitedLengthInputStream extends FilterInputStream {
+    /**
+     * The end of the stream where we don't want to allow more data to be read.
+     */
+    private final int mEnd;
+
+    /**
+     * Current offset in the stream.
+     */
+    private int mOffset;
+
+    /**
+     * @param in underlying stream to wrap
+     * @param offset offset into stream where data starts
+     * @param length length of data at offset
+     * @throws IOException if an error occured with the underlying stream
+     */
+    public LimitedLengthInputStream(InputStream in, int offset, int length) throws IOException {
+        super(in);
+
+        if (in == null) {
+            throw new IOException("in == null");
+        }
+
+        if (offset < 0) {
+            throw new IOException("offset == " + offset);
+        }
+
+        if (length < 0) {
+            throw new IOException("length must be non-negative; is " + length);
+        }
+
+        mEnd = offset + length;
+
+        skip(offset);
+        mOffset = offset;
+    }
+
+    @Override
+    public synchronized int read() throws IOException {
+        if (mOffset >= mEnd) {
+            return -1;
+        }
+
+        mOffset++;
+        return super.read();
+    }
+
+    @Override
+    public int read(byte[] buffer, int offset, int byteCount) throws IOException {
+        if (mOffset >= mEnd) {
+            return -1;
+        }
+
+        if (mOffset + byteCount > mEnd) {
+            byteCount = mEnd - mOffset;
+        }
+
+        final int numRead = super.read(buffer, offset, byteCount);
+        mOffset += numRead;
+
+        return numRead;
+    }
+
+    @Override
+    public int read(byte[] buffer) throws IOException {
+        return read(buffer, 0, buffer.length);
+    }
+}
diff --git a/core/java/android/content/pm/MacAuthenticatedInputStream.java b/core/java/android/content/pm/MacAuthenticatedInputStream.java
new file mode 100644
index 0000000..11f4b94
--- /dev/null
+++ b/core/java/android/content/pm/MacAuthenticatedInputStream.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2012 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 java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.crypto.Mac;
+
+/**
+ * An input stream filter that applies a MAC to the data passing through it. At
+ * the end of the data that should be authenticated, the tag can be calculated.
+ * After that, the stream should not be used.
+ *
+ * @hide
+ */
+public class MacAuthenticatedInputStream extends FilterInputStream {
+    private final Mac mMac;
+
+    public MacAuthenticatedInputStream(InputStream in, Mac mac) {
+        super(in);
+
+        mMac = mac;
+    }
+
+    public boolean isTagEqual(byte[] tag) {
+        final byte[] actualTag = mMac.doFinal();
+
+        if (tag == null || actualTag == null || tag.length != actualTag.length) {
+            return false;
+        }
+
+        /*
+         * Attempt to prevent timing attacks by doing the same amount of work
+         * whether the first byte matches or not. Do not change this to a loop
+         * that exits early when a byte does not match.
+         */
+        int value = 0;
+        for (int i = 0; i < tag.length; i++) {
+            value |= tag[i] ^ actualTag[i];
+        }
+
+        return value == 0;
+    }
+
+    @Override
+    public int read() throws IOException {
+        final int b = super.read();
+        if (b >= 0) {
+            mMac.update((byte) b);
+        }
+        return b;
+    }
+
+    @Override
+    public int read(byte[] buffer, int offset, int count) throws IOException {
+        int numRead = super.read(buffer, offset, count);
+        if (numRead > 0) {
+            mMac.update(buffer, offset, numRead);
+        }
+        return numRead;
+    }
+}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index c3ce1cf..a48924e 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -28,7 +28,6 @@
 import android.content.res.XmlResourceParser;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
-import android.os.Build;
 import android.os.Environment;
 import android.util.AndroidException;
 import android.util.DisplayMetrics;
@@ -2199,12 +2198,19 @@
      *            is performing the installation. This identifies which market
      *            the package came from.
      * @param verificationURI The location of the supplementary verification
-     *            file. This can be a 'file:' or a 'content:' URI.
+     *            file. This can be a 'file:' or a 'content:' URI. May be
+     *            {@code null}.
+     * @param manifestDigest an object that holds the digest of the package
+     *            which can be used to verify ownership. May be {@code null}.
+     * @param encryptionParams if the package to be installed is encrypted,
+     *            these parameters describing the encryption and authentication
+     *            used. May be {@code null}.
      * @hide
      */
     public abstract void installPackageWithVerification(Uri packageURI,
             IPackageInstallObserver observer, int flags, String installerPackageName,
-            Uri verificationURI, ManifestDigest manifestDigest);
+            Uri verificationURI, ManifestDigest manifestDigest,
+            ContainerEncryptionParams encryptionParams);
 
     /**
      * Allows a package listening to the
diff --git a/core/java/com/android/internal/app/IMediaContainerService.aidl b/core/java/com/android/internal/app/IMediaContainerService.aidl
index 727c094..c9f7a58 100755
--- a/core/java/com/android/internal/app/IMediaContainerService.aidl
+++ b/core/java/com/android/internal/app/IMediaContainerService.aidl
@@ -18,6 +18,7 @@
 
 import android.net.Uri;
 import android.os.ParcelFileDescriptor;
+import android.content.pm.ContainerEncryptionParams;
 import android.content.pm.PackageInfoLite;
 import android.content.res.ObbInfo;
 
@@ -25,9 +26,9 @@
     String copyResourceToContainer(in Uri packageURI, String containerId, String key,
             String resFileName, String publicResFileName, boolean isExternal,
             boolean isForwardLocked);
-    int copyResource(in Uri packageURI,
-                in ParcelFileDescriptor outStream);
-    PackageInfoLite getMinimalPackageInfo(in Uri fileUri, in int flags, in long threshold);
+    int copyResource(in Uri packageURI, in ContainerEncryptionParams encryptionParams,
+            in ParcelFileDescriptor outStream);
+    PackageInfoLite getMinimalPackageInfo(in String packagePath, in int flags, in long threshold);
     boolean checkInternalFreeStorage(in Uri fileUri, boolean isForwardLocked, in long threshold);
     boolean checkExternalFreeStorage(in Uri fileUri, boolean isForwardLocked);
     ObbInfo getObbInfo(in String filename);
diff --git a/core/tests/coretests/src/android/content/pm/ContainerEncryptionParamsTest.java b/core/tests/coretests/src/android/content/pm/ContainerEncryptionParamsTest.java
new file mode 100644
index 0000000..7deaa9a
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/ContainerEncryptionParamsTest.java
@@ -0,0 +1,370 @@
+/*
+ * Copyright (C) 2012 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.os.Parcel;
+import android.test.AndroidTestCase;
+
+import java.util.Arrays;
+
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+public class ContainerEncryptionParamsTest extends AndroidTestCase {
+    private static final String ENC_ALGORITHM = "AES/CBC/PKCS7Padding";
+
+    private static final byte[] IV_BYTES = "FOOBAR".getBytes();
+
+    private static final IvParameterSpec ENC_PARAMS = new IvParameterSpec(IV_BYTES);
+
+    private static final byte[] ENC_KEY_BYTES = "abcd1234wxyz7890".getBytes();
+
+    private static final SecretKey ENC_KEY = new SecretKeySpec(ENC_KEY_BYTES, "RAW");
+
+    private static final String MAC_ALGORITHM = "HMAC-SHA1";
+
+    private static final byte[] MAC_KEY_BYTES = "4wxyzabcd1237890".getBytes();
+
+    private static final SecretKey MAC_KEY = new SecretKeySpec(MAC_KEY_BYTES, "RAW");
+
+    private static final byte[] MAC_TAG = "faketag".getBytes();
+
+    private static final int AUTHENTICATED_START = 5;
+
+    private static final int ENCRYPTED_START = 11;
+
+    private static final int DATA_END = 19;
+
+    public void testParcel() throws Exception {
+        ContainerEncryptionParams expected = new ContainerEncryptionParams(ENC_ALGORITHM,
+                ENC_PARAMS, ENC_KEY, MAC_ALGORITHM, null, MAC_KEY, MAC_TAG, AUTHENTICATED_START,
+                ENCRYPTED_START, DATA_END);
+
+        Parcel parcel = Parcel.obtain();
+        expected.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        ContainerEncryptionParams actual = ContainerEncryptionParams.CREATOR
+                .createFromParcel(parcel);
+
+        assertEquals(ENC_ALGORITHM, actual.getEncryptionAlgorithm());
+
+        if (!(actual.getEncryptionSpec() instanceof IvParameterSpec)) {
+            fail("encryption parameters should be IvParameterSpec");
+        } else {
+            IvParameterSpec actualParams = (IvParameterSpec) actual.getEncryptionSpec();
+            assertTrue(Arrays.equals(IV_BYTES, actualParams.getIV()));
+        }
+
+        assertEquals(ENC_KEY, actual.getEncryptionKey());
+
+        assertEquals(MAC_ALGORITHM, actual.getMacAlgorithm());
+
+        assertNull(actual.getMacSpec());
+
+        assertEquals(MAC_KEY, actual.getMacKey());
+
+        assertTrue(Arrays.equals(MAC_TAG, actual.getMacTag()));
+
+        assertEquals(AUTHENTICATED_START, actual.getAuthenticatedDataStart());
+
+        assertEquals(ENCRYPTED_START, actual.getEncryptedDataStart());
+
+        assertEquals(DATA_END, actual.getDataEnd());
+    }
+
+    public void testEquals_Success() throws Exception {
+        ContainerEncryptionParams params1 = new ContainerEncryptionParams(ENC_ALGORITHM,
+                ENC_PARAMS, ENC_KEY, MAC_ALGORITHM, null, MAC_KEY, MAC_TAG, AUTHENTICATED_START,
+                ENCRYPTED_START, DATA_END);
+
+        ContainerEncryptionParams params2 = new ContainerEncryptionParams(
+                new String(ENC_ALGORITHM), new IvParameterSpec(IV_BYTES.clone()),
+                new SecretKeySpec(ENC_KEY_BYTES.clone(), "RAW"), new String(MAC_ALGORITHM), null,
+                new SecretKeySpec(MAC_KEY_BYTES.clone(), "RAW"), MAC_TAG, AUTHENTICATED_START,
+                ENCRYPTED_START, DATA_END);
+
+        assertEquals(params1, params2);
+    }
+
+    public void testEquals_EncAlgo_Failure() throws Exception {
+        ContainerEncryptionParams params1 = new ContainerEncryptionParams(ENC_ALGORITHM,
+                ENC_PARAMS, ENC_KEY, MAC_ALGORITHM, null, MAC_KEY, MAC_TAG, AUTHENTICATED_START,
+                ENCRYPTED_START, DATA_END);
+
+        ContainerEncryptionParams params2 = new ContainerEncryptionParams(new String(
+                "AES-256/CBC/PKCS7Padding"), new IvParameterSpec(IV_BYTES.clone()),
+                new SecretKeySpec(ENC_KEY_BYTES.clone(), "RAW"), new String(MAC_ALGORITHM), null,
+                new SecretKeySpec(MAC_KEY_BYTES.clone(), "RAW"), MAC_TAG, AUTHENTICATED_START,
+                ENCRYPTED_START, DATA_END);
+
+        assertFalse(params1.equals(params2));
+    }
+
+    public void testEquals_EncParams_Failure() throws Exception {
+        ContainerEncryptionParams params1 = new ContainerEncryptionParams(ENC_ALGORITHM,
+                ENC_PARAMS, ENC_KEY, MAC_ALGORITHM, null, MAC_KEY, MAC_TAG, AUTHENTICATED_START,
+                ENCRYPTED_START, DATA_END);
+
+        ContainerEncryptionParams params2 = new ContainerEncryptionParams(
+                new String(ENC_ALGORITHM), new IvParameterSpec("BLAHBLAH".getBytes()),
+                new SecretKeySpec(ENC_KEY_BYTES.clone(), "RAW"), new String(MAC_ALGORITHM), null,
+                new SecretKeySpec(MAC_KEY_BYTES.clone(), "RAW"), MAC_TAG, AUTHENTICATED_START,
+                ENCRYPTED_START, DATA_END);
+
+        assertFalse(params1.equals(params2));
+    }
+
+    public void testEquals_EncKey_Failure() throws Exception {
+        ContainerEncryptionParams params1 = new ContainerEncryptionParams(ENC_ALGORITHM,
+                ENC_PARAMS, ENC_KEY, MAC_ALGORITHM, null, MAC_KEY, MAC_TAG, AUTHENTICATED_START,
+                ENCRYPTED_START, DATA_END);
+
+        ContainerEncryptionParams params2 = new ContainerEncryptionParams(
+                new String(ENC_ALGORITHM), new IvParameterSpec(IV_BYTES.clone()),
+                new SecretKeySpec("BLAHBLAH".getBytes(), "RAW"), new String(MAC_ALGORITHM), null,
+                new SecretKeySpec(MAC_KEY_BYTES.clone(), "RAW"), MAC_TAG, AUTHENTICATED_START,
+                ENCRYPTED_START, DATA_END);
+
+        assertFalse(params1.equals(params2));
+    }
+
+    public void testEquals_MacAlgo_Failure() throws Exception {
+        ContainerEncryptionParams params1 = new ContainerEncryptionParams(ENC_ALGORITHM,
+                ENC_PARAMS, ENC_KEY, MAC_ALGORITHM, null, MAC_KEY, MAC_TAG, AUTHENTICATED_START,
+                ENCRYPTED_START, DATA_END);
+
+        ContainerEncryptionParams params2 = new ContainerEncryptionParams(
+                new String(ENC_ALGORITHM), new IvParameterSpec(IV_BYTES.clone()),
+                new SecretKeySpec(ENC_KEY_BYTES.clone(), "RAW"), "BLAHBLAH", null,
+                new SecretKeySpec(MAC_KEY_BYTES.clone(), "RAW"), MAC_TAG, AUTHENTICATED_START,
+                ENCRYPTED_START, DATA_END);
+
+        assertFalse(params1.equals(params2));
+    }
+
+    public void testEquals_MacKey_Failure() throws Exception {
+        ContainerEncryptionParams params1 = new ContainerEncryptionParams(ENC_ALGORITHM,
+                ENC_PARAMS, ENC_KEY, MAC_ALGORITHM, null, MAC_KEY, MAC_TAG, AUTHENTICATED_START,
+                ENCRYPTED_START, DATA_END);
+
+        ContainerEncryptionParams params2 = new ContainerEncryptionParams(
+                new String(ENC_ALGORITHM), new IvParameterSpec(IV_BYTES.clone()),
+                new SecretKeySpec(ENC_KEY_BYTES.clone(), "RAW"), new String(MAC_ALGORITHM), null,
+                new SecretKeySpec("FAKE_MAC_KEY".getBytes(), "RAW"), MAC_TAG, AUTHENTICATED_START,
+                ENCRYPTED_START, DATA_END);
+
+        assertFalse(params1.equals(params2));
+    }
+
+    public void testEquals_MacTag_Failure() throws Exception {
+        ContainerEncryptionParams params1 = new ContainerEncryptionParams(ENC_ALGORITHM,
+                ENC_PARAMS, ENC_KEY, MAC_ALGORITHM, null, MAC_KEY, MAC_TAG, AUTHENTICATED_START,
+                ENCRYPTED_START, DATA_END);
+
+        ContainerEncryptionParams params2 = new ContainerEncryptionParams(
+                new String(ENC_ALGORITHM), new IvParameterSpec(IV_BYTES.clone()),
+                new SecretKeySpec(ENC_KEY_BYTES.clone(), "RAW"), new String(MAC_ALGORITHM), null,
+                new SecretKeySpec(MAC_KEY_BYTES.clone(), "RAW"), "broken".getBytes(),
+                AUTHENTICATED_START, ENCRYPTED_START, DATA_END);
+
+        assertFalse(params1.equals(params2));
+    }
+
+    public void testEquals_AuthenticatedStart_Failure() throws Exception {
+        ContainerEncryptionParams params1 = new ContainerEncryptionParams(ENC_ALGORITHM,
+                ENC_PARAMS, ENC_KEY, MAC_ALGORITHM, null, MAC_KEY, MAC_TAG, AUTHENTICATED_START,
+                ENCRYPTED_START, DATA_END);
+
+        ContainerEncryptionParams params2 = new ContainerEncryptionParams(
+                new String(ENC_ALGORITHM), new IvParameterSpec(IV_BYTES.clone()),
+                new SecretKeySpec(ENC_KEY_BYTES.clone(), "RAW"), new String(MAC_ALGORITHM), null,
+                new SecretKeySpec(MAC_KEY_BYTES.clone(), "RAW"), MAC_TAG, AUTHENTICATED_START - 1,
+                ENCRYPTED_START, DATA_END);
+
+        assertFalse(params1.equals(params2));
+    }
+
+    public void testEquals_EncryptedStart_Failure() throws Exception {
+        ContainerEncryptionParams params1 = new ContainerEncryptionParams(ENC_ALGORITHM,
+                ENC_PARAMS, ENC_KEY, MAC_ALGORITHM, null, MAC_KEY, MAC_TAG, AUTHENTICATED_START,
+                ENCRYPTED_START, DATA_END);
+
+        ContainerEncryptionParams params2 = new ContainerEncryptionParams(
+                new String(ENC_ALGORITHM), new IvParameterSpec(IV_BYTES.clone()),
+                new SecretKeySpec(ENC_KEY_BYTES.clone(), "RAW"), new String(MAC_ALGORITHM), null,
+                new SecretKeySpec(MAC_KEY_BYTES.clone(), "RAW"), MAC_TAG, AUTHENTICATED_START,
+                ENCRYPTED_START - 1, DATA_END);
+
+        assertFalse(params1.equals(params2));
+    }
+
+    public void testEquals_DataEnd_Failure() throws Exception {
+        ContainerEncryptionParams params1 = new ContainerEncryptionParams(ENC_ALGORITHM,
+                ENC_PARAMS, ENC_KEY, MAC_ALGORITHM, null, MAC_KEY, MAC_TAG, AUTHENTICATED_START,
+                ENCRYPTED_START, DATA_END);
+
+        ContainerEncryptionParams params2 = new ContainerEncryptionParams(
+                new String(ENC_ALGORITHM), new IvParameterSpec(IV_BYTES.clone()),
+                new SecretKeySpec(ENC_KEY_BYTES.clone(), "RAW"), new String(MAC_ALGORITHM), null,
+                new SecretKeySpec(MAC_KEY_BYTES.clone(), "RAW"), MAC_TAG, AUTHENTICATED_START,
+                ENCRYPTED_START, DATA_END + 1);
+
+        assertFalse(params1.equals(params2));
+    }
+
+    public void testHashCode_Success() throws Exception {
+        ContainerEncryptionParams params1 = new ContainerEncryptionParams(ENC_ALGORITHM,
+                ENC_PARAMS, ENC_KEY, MAC_ALGORITHM, null, MAC_KEY, MAC_TAG, AUTHENTICATED_START,
+                ENCRYPTED_START, DATA_END);
+
+        ContainerEncryptionParams params2 = new ContainerEncryptionParams(
+                new String(ENC_ALGORITHM), new IvParameterSpec(IV_BYTES.clone()),
+                new SecretKeySpec(ENC_KEY_BYTES.clone(), "RAW"), new String(MAC_ALGORITHM), null,
+                new SecretKeySpec(MAC_KEY_BYTES.clone(), "RAW"), MAC_TAG, AUTHENTICATED_START,
+                ENCRYPTED_START, DATA_END);
+
+        assertEquals(params1.hashCode(), params2.hashCode());
+    }
+
+    public void testHashCode_EncAlgo_Failure() throws Exception {
+        ContainerEncryptionParams params1 = new ContainerEncryptionParams(ENC_ALGORITHM,
+                ENC_PARAMS, ENC_KEY, MAC_ALGORITHM, null, MAC_KEY, MAC_TAG, AUTHENTICATED_START,
+                ENCRYPTED_START, DATA_END);
+
+        ContainerEncryptionParams params2 = new ContainerEncryptionParams(new String(
+                "AES-256/CBC/PKCS7Padding"), new IvParameterSpec(IV_BYTES.clone()),
+                new SecretKeySpec(ENC_KEY_BYTES.clone(), "RAW"), new String(MAC_ALGORITHM), null,
+                new SecretKeySpec(MAC_KEY_BYTES.clone(), "RAW"), MAC_TAG, AUTHENTICATED_START,
+                ENCRYPTED_START, DATA_END);
+
+        assertFalse(params1.hashCode() == params2.hashCode());
+    }
+
+    public void testHashCode_EncParams_Failure() throws Exception {
+        ContainerEncryptionParams params1 = new ContainerEncryptionParams(ENC_ALGORITHM,
+                ENC_PARAMS, ENC_KEY, MAC_ALGORITHM, null, MAC_KEY, MAC_TAG, AUTHENTICATED_START,
+                ENCRYPTED_START, DATA_END);
+
+        ContainerEncryptionParams params2 = new ContainerEncryptionParams(
+                new String(ENC_ALGORITHM), new IvParameterSpec("BLAHBLAH".getBytes()),
+                new SecretKeySpec(ENC_KEY_BYTES.clone(), "RAW"), new String(MAC_ALGORITHM), null,
+                new SecretKeySpec(MAC_KEY_BYTES.clone(), "RAW"), MAC_TAG, AUTHENTICATED_START,
+                ENCRYPTED_START, DATA_END);
+
+        assertFalse(params1.hashCode() == params2.hashCode());
+    }
+
+    public void testHashCode_EncKey_Failure() throws Exception {
+        ContainerEncryptionParams params1 = new ContainerEncryptionParams(ENC_ALGORITHM,
+                ENC_PARAMS, ENC_KEY, MAC_ALGORITHM, null, MAC_KEY, MAC_TAG, AUTHENTICATED_START,
+                ENCRYPTED_START, DATA_END);
+
+        ContainerEncryptionParams params2 = new ContainerEncryptionParams(
+                new String(ENC_ALGORITHM), new IvParameterSpec(IV_BYTES.clone()),
+                new SecretKeySpec("BLAHBLAH".getBytes(), "RAW"), new String(MAC_ALGORITHM), null,
+                new SecretKeySpec(MAC_KEY_BYTES.clone(), "RAW"), MAC_TAG, AUTHENTICATED_START,
+                ENCRYPTED_START, DATA_END);
+
+        assertFalse(params1.hashCode() == params2.hashCode());
+    }
+
+    public void testHashCode_MacAlgo_Failure() throws Exception {
+        ContainerEncryptionParams params1 = new ContainerEncryptionParams(ENC_ALGORITHM,
+                ENC_PARAMS, ENC_KEY, MAC_ALGORITHM, null, MAC_KEY, MAC_TAG, AUTHENTICATED_START,
+                ENCRYPTED_START, DATA_END);
+
+        ContainerEncryptionParams params2 = new ContainerEncryptionParams(
+                new String(ENC_ALGORITHM), new IvParameterSpec(IV_BYTES.clone()),
+                new SecretKeySpec(ENC_KEY_BYTES.clone(), "RAW"), "BLAHBLAH", null,
+                new SecretKeySpec(MAC_KEY_BYTES.clone(), "RAW"), MAC_TAG, AUTHENTICATED_START,
+                ENCRYPTED_START, DATA_END);
+
+        assertFalse(params1.hashCode() == params2.hashCode());
+    }
+
+    public void testHashCode_MacKey_Failure() throws Exception {
+        ContainerEncryptionParams params1 = new ContainerEncryptionParams(ENC_ALGORITHM,
+                ENC_PARAMS, ENC_KEY, MAC_ALGORITHM, null, MAC_KEY, MAC_TAG, AUTHENTICATED_START,
+                ENCRYPTED_START, DATA_END);
+
+        ContainerEncryptionParams params2 = new ContainerEncryptionParams(
+                new String(ENC_ALGORITHM), new IvParameterSpec(IV_BYTES.clone()),
+                new SecretKeySpec(ENC_KEY_BYTES.clone(), "RAW"), new String(MAC_ALGORITHM), null,
+                new SecretKeySpec("FAKE_MAC_KEY".getBytes(), "RAW"), MAC_TAG, AUTHENTICATED_START,
+                ENCRYPTED_START, DATA_END);
+
+        assertFalse(params1.hashCode() == params2.hashCode());
+    }
+
+    public void testHashCode_MacTag_Failure() throws Exception {
+        ContainerEncryptionParams params1 = new ContainerEncryptionParams(ENC_ALGORITHM,
+                ENC_PARAMS, ENC_KEY, MAC_ALGORITHM, null, MAC_KEY, MAC_TAG, AUTHENTICATED_START,
+                ENCRYPTED_START, DATA_END);
+
+        ContainerEncryptionParams params2 = new ContainerEncryptionParams(
+                new String(ENC_ALGORITHM), new IvParameterSpec(IV_BYTES.clone()),
+                new SecretKeySpec(ENC_KEY_BYTES.clone(), "RAW"), new String(MAC_ALGORITHM), null,
+                new SecretKeySpec(MAC_KEY_BYTES.clone(), "RAW"), "broken".getBytes(),
+                AUTHENTICATED_START, ENCRYPTED_START, DATA_END);
+
+        assertFalse(params1.hashCode() == params2.hashCode());
+    }
+
+    public void testHashCode_AuthenticatedStart_Failure() throws Exception {
+        ContainerEncryptionParams params1 = new ContainerEncryptionParams(ENC_ALGORITHM,
+                ENC_PARAMS, ENC_KEY, MAC_ALGORITHM, null, MAC_KEY, MAC_TAG, AUTHENTICATED_START,
+                ENCRYPTED_START, DATA_END);
+
+        ContainerEncryptionParams params2 = new ContainerEncryptionParams(
+                new String(ENC_ALGORITHM), new IvParameterSpec(IV_BYTES.clone()),
+                new SecretKeySpec(ENC_KEY_BYTES.clone(), "RAW"), new String(MAC_ALGORITHM), null,
+                new SecretKeySpec(MAC_KEY_BYTES.clone(), "RAW"), MAC_TAG, AUTHENTICATED_START - 1,
+                ENCRYPTED_START, DATA_END);
+
+        assertFalse(params1.hashCode() == params2.hashCode());
+    }
+
+    public void testHashCode_EncryptedStart_Failure() throws Exception {
+        ContainerEncryptionParams params1 = new ContainerEncryptionParams(ENC_ALGORITHM,
+                ENC_PARAMS, ENC_KEY, MAC_ALGORITHM, null, MAC_KEY, MAC_TAG, AUTHENTICATED_START,
+                ENCRYPTED_START, DATA_END);
+
+        ContainerEncryptionParams params2 = new ContainerEncryptionParams(
+                new String(ENC_ALGORITHM), new IvParameterSpec(IV_BYTES.clone()),
+                new SecretKeySpec(ENC_KEY_BYTES.clone(), "RAW"), new String(MAC_ALGORITHM), null,
+                new SecretKeySpec(MAC_KEY_BYTES.clone(), "RAW"), MAC_TAG, AUTHENTICATED_START,
+                ENCRYPTED_START - 1, DATA_END);
+
+        assertFalse(params1.hashCode() == params2.hashCode());
+    }
+
+    public void testHashCode_DataEnd_Failure() throws Exception {
+        ContainerEncryptionParams params1 = new ContainerEncryptionParams(ENC_ALGORITHM,
+                ENC_PARAMS, ENC_KEY, MAC_ALGORITHM, null, MAC_KEY, MAC_TAG, AUTHENTICATED_START,
+                ENCRYPTED_START, DATA_END);
+
+        ContainerEncryptionParams params2 = new ContainerEncryptionParams(
+                new String(ENC_ALGORITHM), new IvParameterSpec(IV_BYTES.clone()),
+                new SecretKeySpec(ENC_KEY_BYTES.clone(), "RAW"), new String(MAC_ALGORITHM), null,
+                new SecretKeySpec(MAC_KEY_BYTES.clone(), "RAW"), MAC_TAG, AUTHENTICATED_START,
+                ENCRYPTED_START, DATA_END + 1);
+
+        assertFalse(params1.hashCode() == params2.hashCode());
+    }
+}
diff --git a/core/tests/coretests/src/android/content/pm/LimitedLengthInputStreamTest.java b/core/tests/coretests/src/android/content/pm/LimitedLengthInputStreamTest.java
new file mode 100644
index 0000000..0a0152b
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/LimitedLengthInputStreamTest.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2012 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.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+
+public class LimitedLengthInputStreamTest extends AndroidTestCase {
+    private final byte[] TEST_STRING1 = "This is a test".getBytes();
+
+    private InputStream mTestStream1;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mTestStream1 = new ByteArrayInputStream(TEST_STRING1);
+    }
+
+    @MediumTest
+    public void testConstructor_NegativeOffset_Failure() throws Exception {
+        try {
+            InputStream is = new LimitedLengthInputStream(mTestStream1, -1, TEST_STRING1.length);
+            fail("Should throw IOException on negative index");
+        } catch (IOException e) {
+            // success
+        }
+    }
+
+    @MediumTest
+    public void testConstructor_NegativeLength_Failure() throws Exception {
+        try {
+            InputStream is = new LimitedLengthInputStream(mTestStream1, 0, -1);
+            fail("Should throw IOException on negative length");
+        } catch (IOException e) {
+            // success
+        }
+    }
+
+    @MediumTest
+    public void testConstructor_NullInputStream_Failure() throws Exception {
+        try {
+            InputStream is = new LimitedLengthInputStream(null, 0, 1);
+            fail("Should throw IOException on null input stream");
+        } catch (IOException e) {
+            // success
+        }
+    }
+
+    private void checkReadBytesWithOffsetAndLength_WithString1(int offset, int length)
+            throws Exception {
+        byte[] temp = new byte[TEST_STRING1.length];
+        byte[] expected = new byte[length];
+        byte[] actual = new byte[length];
+
+        System.arraycopy(TEST_STRING1, offset, expected, 0, length);
+
+        InputStream is = new LimitedLengthInputStream(mTestStream1, offset, length);
+        assertEquals(length, is.read(temp, 0, temp.length));
+
+        System.arraycopy(temp, 0, actual, 0, length);
+        assertTrue(Arrays.equals(expected, actual));
+
+        assertEquals(-1, is.read(temp, 0, temp.length));
+    }
+
+    @MediumTest
+    public void testReadBytesWithOffsetAndLength_ZeroOffset_PartialLength_Success()
+            throws Exception {
+        checkReadBytesWithOffsetAndLength_WithString1(0, 2);
+    }
+
+    @MediumTest
+    public void testReadBytesWithOffsetAndLength_NonZeroOffset_PartialLength_Success()
+            throws Exception {
+        checkReadBytesWithOffsetAndLength_WithString1(3, 2);
+    }
+
+    @MediumTest
+    public void testReadBytesWithOffsetAndLength_ZeroOffset_FullLength_Success() throws Exception {
+        checkReadBytesWithOffsetAndLength_WithString1(0, TEST_STRING1.length);
+    }
+
+    @MediumTest
+    public void testReadBytesWithOffsetAndLength_NonZeroOffset_FullLength_Success()
+            throws Exception {
+        checkReadBytesWithOffsetAndLength_WithString1(3, TEST_STRING1.length - 3);
+    }
+
+    @MediumTest
+    public void testReadBytesWithOffsetAndLength_ZeroOffset_PastEnd_Success() throws Exception {
+        byte[] temp = new byte[TEST_STRING1.length + 10];
+        InputStream is = new LimitedLengthInputStream(mTestStream1, 0, TEST_STRING1.length + 10);
+        assertEquals(TEST_STRING1.length, is.read(temp, 0, TEST_STRING1.length + 10));
+
+        byte[] actual = new byte[TEST_STRING1.length];
+        System.arraycopy(temp, 0, actual, 0, actual.length);
+        assertTrue(Arrays.equals(TEST_STRING1, actual));
+    }
+
+    private void checkReadBytes_WithString1(int offset, int length) throws Exception {
+        byte[] temp = new byte[TEST_STRING1.length];
+        byte[] expected = new byte[length];
+        byte[] actual = new byte[length];
+
+        System.arraycopy(TEST_STRING1, offset, expected, 0, length);
+
+        InputStream is = new LimitedLengthInputStream(mTestStream1, offset, length);
+        assertEquals(length, is.read(temp));
+
+        System.arraycopy(temp, 0, actual, 0, length);
+        assertTrue(Arrays.equals(expected, actual));
+
+        assertEquals(-1, is.read(temp));
+    }
+
+    @MediumTest
+    public void testReadBytes_ZeroOffset_PartialLength_Success() throws Exception {
+        checkReadBytesWithOffsetAndLength_WithString1(0, 2);
+    }
+
+    @MediumTest
+    public void testReadBytes_NonZeroOffset_PartialLength_Success() throws Exception {
+        checkReadBytesWithOffsetAndLength_WithString1(3, 2);
+    }
+
+    @MediumTest
+    public void testReadBytes_ZeroOffset_FullLength_Success() throws Exception {
+        checkReadBytesWithOffsetAndLength_WithString1(0, TEST_STRING1.length);
+    }
+
+    @MediumTest
+    public void testReadBytes_NonZeroOffset_FullLength_Success() throws Exception {
+        checkReadBytesWithOffsetAndLength_WithString1(3, TEST_STRING1.length - 3);
+    }
+
+    private void checkSingleByteRead_WithString1(int offset, int length) throws Exception {
+        InputStream is = new LimitedLengthInputStream(mTestStream1, offset, length);
+
+        for (int i = 0; i < length; i++) {
+            assertEquals(TEST_STRING1[offset + i], is.read());
+        }
+
+        assertEquals(-1, is.read());
+    }
+
+    @MediumTest
+    public void testSingleByteRead_ZeroOffset_PartialLength_Success() throws Exception {
+        checkSingleByteRead_WithString1(0, 2);
+    }
+
+    @MediumTest
+    public void testSingleByteRead_NonZeroOffset_PartialLength_Success() throws Exception {
+        checkSingleByteRead_WithString1(3, 2);
+    }
+
+    @MediumTest
+    public void testSingleByteRead_ZeroOffset_FullLength_Success() throws Exception {
+        checkSingleByteRead_WithString1(0, TEST_STRING1.length);
+    }
+
+    @MediumTest
+    public void testSingleByteRead_NonZeroOffset_FullLength_Success() throws Exception {
+        checkSingleByteRead_WithString1(3, TEST_STRING1.length - 3);
+    }
+
+}
diff --git a/core/tests/coretests/src/android/content/pm/MacAuthenticatedInputStreamTest.java b/core/tests/coretests/src/android/content/pm/MacAuthenticatedInputStreamTest.java
new file mode 100644
index 0000000..948e722
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/MacAuthenticatedInputStreamTest.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2012 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.test.AndroidTestCase;
+
+import java.io.ByteArrayInputStream;
+import java.util.Arrays;
+
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+
+import libcore.io.Streams;
+
+public class MacAuthenticatedInputStreamTest extends AndroidTestCase {
+
+    private static final SecretKey HMAC_KEY_1 = new SecretKeySpec("test_key_1".getBytes(), "HMAC");
+
+    private static final byte[] TEST_STRING_1 = "Hello, World!".getBytes();
+
+    /**
+     * Generated with:
+     *
+     * echo -n 'Hello, World!' | openssl dgst -hmac 'test_key_1' -binary -sha1 | recode ..//x1 |
+     *   sed 's/0x/(byte) 0x/g'
+     */
+    private static final byte[] TEST_STRING_1_MAC = {
+            (byte) 0x29, (byte) 0xB1, (byte) 0x87, (byte) 0x6B, (byte) 0xFE, (byte) 0x83,
+            (byte) 0x96, (byte) 0x51, (byte) 0x61, (byte) 0x02, (byte) 0xAF, (byte) 0x7B,
+            (byte) 0xBA, (byte) 0x05, (byte) 0xE6, (byte) 0xA4, (byte) 0xAB, (byte) 0x36,
+            (byte) 0x18, (byte) 0x02
+    };
+
+    /**
+     * Same as TEST_STRING_1_MAC but with the first byte as 0x28 instead of
+     * 0x29.
+     */
+    private static final byte[] TEST_STRING_1_MAC_BROKEN = {
+            (byte) 0x28, (byte) 0xB1, (byte) 0x87, (byte) 0x6B, (byte) 0xFE, (byte) 0x83,
+            (byte) 0x96, (byte) 0x51, (byte) 0x61, (byte) 0x02, (byte) 0xAF, (byte) 0x7B,
+            (byte) 0xBA, (byte) 0x05, (byte) 0xE6, (byte) 0xA4, (byte) 0xAB, (byte) 0x36,
+            (byte) 0x18, (byte) 0x02
+    };
+
+    private ByteArrayInputStream mTestStream1;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mTestStream1 = new ByteArrayInputStream(TEST_STRING_1);
+    }
+
+    public void testString1Authenticate_Success() throws Exception {
+        Mac mac = Mac.getInstance("HMAC-SHA1");
+        mac.init(HMAC_KEY_1);
+
+        MacAuthenticatedInputStream is = new MacAuthenticatedInputStream(mTestStream1, mac);
+
+        assertTrue(Arrays.equals(TEST_STRING_1, Streams.readFully(is)));
+
+        assertTrue(is.isTagEqual(TEST_STRING_1_MAC));
+    }
+
+    public void testString1Authenticate_WrongTag_Failure() throws Exception {
+        Mac mac = Mac.getInstance("HMAC-SHA1");
+        mac.init(HMAC_KEY_1);
+
+        MacAuthenticatedInputStream is = new MacAuthenticatedInputStream(mTestStream1, mac);
+
+        assertTrue(Arrays.equals(TEST_STRING_1, Streams.readFully(is)));
+
+        assertFalse(is.isTagEqual(TEST_STRING_1_MAC_BROKEN));
+    }
+
+    public void testString1Authenticate_NullTag_Failure() throws Exception {
+        Mac mac = Mac.getInstance("HMAC-SHA1");
+        mac.init(HMAC_KEY_1);
+
+        MacAuthenticatedInputStream is = new MacAuthenticatedInputStream(mTestStream1, mac);
+
+        assertTrue(Arrays.equals(TEST_STRING_1, Streams.readFully(is)));
+
+        assertFalse(is.isTagEqual(null));
+    }
+
+    public void testString1Authenticate_ReadSingleByte_Success() throws Exception {
+        Mac mac = Mac.getInstance("HMAC-SHA1");
+        mac.init(HMAC_KEY_1);
+
+        MacAuthenticatedInputStream is = new MacAuthenticatedInputStream(mTestStream1, mac);
+
+        int numRead = 0;
+        while (is.read() != -1) {
+            numRead++;
+
+            if (numRead > TEST_STRING_1.length) {
+                fail("read too many bytes");
+            }
+        }
+        assertEquals(TEST_STRING_1.length, numRead);
+
+        assertTrue(is.isTagEqual(TEST_STRING_1_MAC));
+    }
+}
diff --git a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
index c709e40..17e5f4e 100644
--- a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
+++ b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
@@ -22,7 +22,10 @@
 
 import android.app.IntentService;
 import android.content.Intent;
+import android.content.pm.MacAuthenticatedInputStream;
+import android.content.pm.ContainerEncryptionParams;
 import android.content.pm.IPackageManager;
+import android.content.pm.LimitedLengthInputStream;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInfoLite;
 import android.content.pm.PackageManager;
@@ -49,9 +52,21 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.security.DigestException;
+import java.security.GeneralSecurityException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherInputStream;
+import javax.crypto.Mac;
+import javax.crypto.NoSuchPaddingException;
 
 import libcore.io.ErrnoException;
+import libcore.io.IoUtils;
 import libcore.io.Libcore;
+import libcore.io.Streams;
 import libcore.io.StructStatFs;
 
 /*
@@ -68,7 +83,7 @@
     private static final String LIB_DIR_NAME = "lib";
 
     private IMediaContainerService.Stub mBinder = new IMediaContainerService.Stub() {
-        /*
+        /**
          * Creates a new container and copies resource there.
          * @param paackageURI the uri of resource to be copied. Can be either
          * a content uri or a file uri
@@ -92,15 +107,19 @@
                     isExternal, isForwardLocked);
         }
 
-        /*
+        /**
          * Copy specified resource to output stream
+         *
          * @param packageURI the uri of resource to be copied. Should be a file
-         * uri
+         *            uri
+         * @param encryptionParams parameters describing the encryption used for
+         *            this file
          * @param outStream Remote file descriptor to be used for copying
-         * @return returns status code according to those in {@link
-         * PackageManager}
+         * @return returns status code according to those in
+         *         {@link PackageManager}
          */
-        public int copyResource(final Uri packageURI, ParcelFileDescriptor outStream) {
+        public int copyResource(final Uri packageURI, ContainerEncryptionParams encryptionParams,
+                ParcelFileDescriptor outStream) {
             if (packageURI == null || outStream == null) {
                 return PackageManager.INSTALL_FAILED_INVALID_URI;
             }
@@ -109,7 +128,7 @@
                     = new ParcelFileDescriptor.AutoCloseOutputStream(outStream);
 
             try {
-                copyFile(packageURI, autoOut);
+                copyFile(packageURI, autoOut, encryptionParams);
                 return PackageManager.INSTALL_SUCCEEDED;
             } catch (FileNotFoundException e) {
                 Slog.e(TAG, "Could not copy URI " + packageURI.toString() + " FNF: "
@@ -119,10 +138,14 @@
                 Slog.e(TAG, "Could not copy URI " + packageURI.toString() + " IO: "
                         + e.getMessage());
                 return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
+            } catch (DigestException e) {
+                Slog.e(TAG, "Could not copy URI " + packageURI.toString() + " Security: "
+                                + e.getMessage());
+                return PackageManager.INSTALL_FAILED_INVALID_APK;
             }
         }
 
-        /*
+        /**
          * Determine the recommended install location for package
          * specified by file uri location.
          * @param fileUri the uri of resource to be copied. Should be a
@@ -130,28 +153,24 @@
          * @return Returns PackageInfoLite object containing
          * the package info and recommended app location.
          */
-        public PackageInfoLite getMinimalPackageInfo(final Uri fileUri, int flags, long threshold) {
+        public PackageInfoLite getMinimalPackageInfo(final String packagePath, int flags,
+                long threshold) {
             PackageInfoLite ret = new PackageInfoLite();
-            if (fileUri == null) {
-                Slog.i(TAG, "Invalid package uri " + fileUri);
+
+            if (packagePath == null) {
+                Slog.i(TAG, "Invalid package file " + packagePath);
                 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
                 return ret;
             }
-            String scheme = fileUri.getScheme();
-            if (scheme != null && !scheme.equals("file")) {
-                Slog.w(TAG, "Falling back to installing on internal storage only");
-                ret.recommendedInstallLocation = PackageHelper.RECOMMEND_INSTALL_INTERNAL;
-                return ret;
-            }
-            String archiveFilePath = fileUri.getPath();
+
             DisplayMetrics metrics = new DisplayMetrics();
             metrics.setToDefaults();
 
-            PackageParser.PackageLite pkg = PackageParser.parsePackageLite(archiveFilePath, 0);
+            PackageParser.PackageLite pkg = PackageParser.parsePackageLite(packagePath, 0);
             if (pkg == null) {
                 Slog.w(TAG, "Failed to parse package");
 
-                final File apkFile = new File(archiveFilePath);
+                final File apkFile = new File(packagePath);
                 if (!apkFile.exists()) {
                     ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_URI;
                 } else {
@@ -160,12 +179,13 @@
 
                 return ret;
             }
+
             ret.packageName = pkg.packageName;
             ret.installLocation = pkg.installLocation;
             ret.verifiers = pkg.verifiers;
 
             ret.recommendedInstallLocation = recommendAppInstallLocation(pkg.installLocation,
-                    archiveFilePath, flags, threshold);
+                    packagePath, flags, threshold);
 
             return ret;
         }
@@ -392,55 +412,195 @@
         }
     }
 
-    private static void copyToFile(File srcFile, OutputStream out)
-            throws FileNotFoundException, IOException {
-        InputStream inputStream = new BufferedInputStream(new FileInputStream(srcFile));
+    private void copyFile(Uri pPackageURI, OutputStream outStream,
+            ContainerEncryptionParams encryptionParams) throws FileNotFoundException, IOException,
+            DigestException {
+        String scheme = pPackageURI.getScheme();
+        InputStream inStream = null;
         try {
-            copyToFile(inputStream, out);
+            if (scheme == null || scheme.equals("file")) {
+                final InputStream is = new FileInputStream(new File(pPackageURI.getPath()));
+                inStream = new BufferedInputStream(is);
+            } else if (scheme.equals("content")) {
+                final ParcelFileDescriptor fd;
+                try {
+                    fd = getContentResolver().openFileDescriptor(pPackageURI, "r");
+                } catch (FileNotFoundException e) {
+                    Slog.e(TAG, "Couldn't open file descriptor from download service. "
+                            + "Failed with exception " + e);
+                    throw e;
+                }
+
+                if (fd == null) {
+                    Slog.e(TAG, "Provider returned no file descriptor for " +
+                            pPackageURI.toString());
+                    throw new FileNotFoundException("provider returned no file descriptor");
+                } else {
+                    if (localLOGV) {
+                        Slog.i(TAG, "Opened file descriptor from download service.");
+                    }
+                    inStream = new ParcelFileDescriptor.AutoCloseInputStream(fd);
+                }
+            } else {
+                Slog.e(TAG, "Package URI is not 'file:' or 'content:' - " + pPackageURI);
+                throw new FileNotFoundException("Package URI is not 'file:' or 'content:'");
+            }
+
+            /*
+             * If this resource is encrypted, get the decrypted stream version
+             * of it.
+             */
+            ApkContainer container = new ApkContainer(inStream, encryptionParams);
+
+            try {
+                /*
+                 * We copy the source package file to a temp file and then
+                 * rename it to the destination file in order to eliminate a
+                 * window where the package directory scanner notices the new
+                 * package file but it's not completely copied yet.
+                 */
+                copyToFile(container.getInputStream(), outStream);
+
+                if (!container.isAuthenticated()) {
+                    throw new DigestException();
+                }
+            } catch (GeneralSecurityException e) {
+                throw new DigestException("A problem occured copying the file.");
+            }
         } finally {
-            try { inputStream.close(); } catch (IOException e) {}
+            IoUtils.closeQuietly(inStream);
         }
     }
 
-    private void copyFile(Uri pPackageURI, OutputStream outStream) throws FileNotFoundException,
-            IOException {
-        String scheme = pPackageURI.getScheme();
-        if (scheme == null || scheme.equals("file")) {
-            final File srcPackageFile = new File(pPackageURI.getPath());
-            // We copy the source package file to a temp file and then rename it to the
-            // destination file in order to eliminate a window where the package directory
-            // scanner notices the new package file but it's not completely copied yet.
-            copyToFile(srcPackageFile, outStream);
-        } else if (scheme.equals("content")) {
-            ParcelFileDescriptor fd = null;
-            try {
-                fd = getContentResolver().openFileDescriptor(pPackageURI, "r");
-            } catch (FileNotFoundException e) {
-                Slog.e(TAG, "Couldn't open file descriptor from download service. "
-                        + "Failed with exception " + e);
-                throw e;
-            }
+    private static class ApkContainer {
+        private final InputStream mInStream;
 
-            if (fd == null) {
-                Slog.e(TAG, "Provider returned no file descriptor for " + pPackageURI.toString());
-                throw new FileNotFoundException("provider returned no file descriptor");
+        private MacAuthenticatedInputStream mAuthenticatedStream;
+
+        private byte[] mTag;
+
+        public ApkContainer(InputStream inStream, ContainerEncryptionParams encryptionParams)
+                throws IOException {
+            if (encryptionParams == null) {
+                mInStream = inStream;
             } else {
-                if (localLOGV) {
-                    Slog.i(TAG, "Opened file descriptor from download service.");
-                }
-                ParcelFileDescriptor.AutoCloseInputStream dlStream
-                        = new ParcelFileDescriptor.AutoCloseInputStream(fd);
-
-                // We copy the source package file to a temp file and then rename it to the
-                // destination file in order to eliminate a window where the package directory
-                // scanner notices the new package file but it's not completely
-                // copied
-                copyToFile(dlStream, outStream);
+                mInStream = getDecryptedStream(inStream, encryptionParams);
+                mTag = encryptionParams.getMacTag();
             }
-        } else {
-            Slog.e(TAG, "Package URI is not 'file:' or 'content:' - " + pPackageURI);
-            throw new FileNotFoundException("Package URI is not 'file:' or 'content:'");
         }
+
+        public boolean isAuthenticated() {
+            if (mAuthenticatedStream == null) {
+                return true;
+            }
+
+            return mAuthenticatedStream.isTagEqual(mTag);
+        }
+
+        private Mac getMacInstance(ContainerEncryptionParams encryptionParams) throws IOException {
+            final Mac m;
+            try {
+                final String macAlgo = encryptionParams.getMacAlgorithm();
+
+                if (macAlgo != null) {
+                    m = Mac.getInstance(macAlgo);
+                    m.init(encryptionParams.getMacKey(), encryptionParams.getMacSpec());
+                } else {
+                    m = null;
+                }
+
+                return m;
+            } catch (NoSuchAlgorithmException e) {
+                throw new IOException(e);
+            } catch (InvalidKeyException e) {
+                throw new IOException(e);
+            } catch (InvalidAlgorithmParameterException e) {
+                throw new IOException(e);
+            }
+        }
+
+        public InputStream getInputStream() {
+            return mInStream;
+        }
+
+        private InputStream getDecryptedStream(InputStream inStream,
+                ContainerEncryptionParams encryptionParams) throws IOException {
+            final Cipher c;
+            try {
+                c = Cipher.getInstance(encryptionParams.getEncryptionAlgorithm());
+                c.init(Cipher.DECRYPT_MODE, encryptionParams.getEncryptionKey(),
+                        encryptionParams.getEncryptionSpec());
+            } catch (NoSuchAlgorithmException e) {
+                throw new IOException(e);
+            } catch (NoSuchPaddingException e) {
+                throw new IOException(e);
+            } catch (InvalidKeyException e) {
+                throw new IOException(e);
+            } catch (InvalidAlgorithmParameterException e) {
+                throw new IOException(e);
+            }
+
+            final int encStart = encryptionParams.getEncryptedDataStart();
+            final int end = encryptionParams.getDataEnd();
+            if (end < encStart) {
+                throw new IOException("end <= encStart");
+            }
+
+            final Mac mac = getMacInstance(encryptionParams);
+            if (mac != null) {
+                final int macStart = encryptionParams.getAuthenticatedDataStart();
+
+                final int furtherOffset;
+                if (macStart >= 0 && encStart >= 0 && macStart < encStart) {
+                    /*
+                     * If there is authenticated data at the beginning, read
+                     * that into our MAC first.
+                     */
+                    final int authenticatedLength = encStart - macStart;
+                    final byte[] authenticatedData = new byte[authenticatedLength];
+
+                    Streams.readFully(inStream, authenticatedData, macStart, authenticatedLength);
+                    mac.update(authenticatedData, 0, authenticatedLength);
+
+                    furtherOffset = 0;
+                } else {
+                    /*
+                     * No authenticated data at the beginning. Just skip the
+                     * required number of bytes to the beginning of the stream.
+                     */
+                    if (encStart > 0) {
+                        furtherOffset = encStart;
+                    } else {
+                        furtherOffset = 0;
+                    }
+                }
+
+                /*
+                 * If there is data at the end of the stream we want to ignore,
+                 * wrap this in a LimitedLengthInputStream.
+                 */
+                if (furtherOffset >= 0 && end > furtherOffset) {
+                    inStream = new LimitedLengthInputStream(inStream, furtherOffset, end - encStart);
+                } else if (furtherOffset > 0) {
+                    inStream.skip(furtherOffset);
+                }
+
+                mAuthenticatedStream = new MacAuthenticatedInputStream(inStream, mac);
+
+                inStream = mAuthenticatedStream;
+            } else {
+                if (encStart >= 0) {
+                    if (end > encStart) {
+                        inStream = new LimitedLengthInputStream(inStream, encStart, end - encStart);
+                    } else {
+                        inStream.skip(encStart);
+                    }
+                }
+            }
+
+            return new CipherInputStream(inStream, c);
+        }
+
     }
 
     private static final int PREFER_INTERNAL = 1;
diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java
index b5d0b60..b5567d3 100644
--- a/services/java/com/android/server/pm/PackageManagerService.java
+++ b/services/java/com/android/server/pm/PackageManagerService.java
@@ -53,6 +53,7 @@
 import android.content.IntentSender.SendIntentException;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.ContainerEncryptionParams;
 import android.content.pm.FeatureInfo;
 import android.content.pm.IPackageDataObserver;
 import android.content.pm.IPackageDeleteObserver;
@@ -116,7 +117,6 @@
 import java.io.FileReader;
 import java.io.FilenameFilter;
 import java.io.IOException;
-import java.io.InputStream;
 import java.io.PrintWriter;
 import java.security.NoSuchAlgorithmException;
 import java.security.PublicKey;
@@ -128,7 +128,6 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.Date;
-import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -136,9 +135,6 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
-import java.util.zip.ZipOutputStream;
 
 import libcore.io.ErrnoException;
 import libcore.io.IoUtils;
@@ -5133,13 +5129,13 @@
             final Uri packageURI, final IPackageInstallObserver observer, final int flags,
             final String installerPackageName) {
         installPackageWithVerification(packageURI, observer, flags, installerPackageName, null,
-                null);
+                null, null);
     }
 
     @Override
     public void installPackageWithVerification(Uri packageURI, IPackageInstallObserver observer,
             int flags, String installerPackageName, Uri verificationURI,
-            ManifestDigest manifestDigest) {
+            ManifestDigest manifestDigest, ContainerEncryptionParams encryptionParams) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, null);
 
         final int uid = Binder.getCallingUid();
@@ -5157,7 +5153,7 @@
 
         final Message msg = mHandler.obtainMessage(INIT_COPY);
         msg.obj = new InstallParams(packageURI, observer, filteredFlags, installerPackageName,
-                verificationURI, manifestDigest);
+                verificationURI, manifestDigest, encryptionParams);
         mHandler.sendMessage(msg);
     }
 
@@ -5560,22 +5556,27 @@
     class InstallParams extends HandlerParams {
         final IPackageInstallObserver observer;
         int flags;
-        final Uri packageURI;
+
+        private final Uri mPackageURI;
         final String installerPackageName;
         final Uri verificationURI;
         final ManifestDigest manifestDigest;
         private InstallArgs mArgs;
         private int mRet;
+        private File mTempPackage;
+        final ContainerEncryptionParams encryptionParams;
 
         InstallParams(Uri packageURI,
                 IPackageInstallObserver observer, int flags,
-                String installerPackageName, Uri verificationURI, ManifestDigest manifestDigest) {
-            this.packageURI = packageURI;
+                String installerPackageName, Uri verificationURI, ManifestDigest manifestDigest,
+                ContainerEncryptionParams encryptionParams) {
+            this.mPackageURI = packageURI;
             this.flags = flags;
             this.observer = observer;
             this.installerPackageName = installerPackageName;
             this.verificationURI = verificationURI;
             this.manifestDigest = manifestDigest;
+            this.encryptionParams = encryptionParams;
         }
 
         private int installLocationPolicy(PackageInfoLite pkgLite, int flags) {
@@ -5655,16 +5656,51 @@
                     lowThreshold = dsm.getMemoryLowThreshold();
                 }
 
-                // Remote call to find out default install location
                 try {
-                    mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, packageURI,
+                    mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, mPackageURI,
                             Intent.FLAG_GRANT_READ_URI_PERMISSION);
-                    pkgLite = mContainerService.getMinimalPackageInfo(packageURI, flags,
-                            lowThreshold);
-                } finally {
-                    mContext.revokeUriPermission(packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION);
-                }
 
+                    final File packageFile;
+                    if (encryptionParams != null || !"file".equals(mPackageURI.getScheme())) {
+                        ParcelFileDescriptor out = null;
+
+                        mTempPackage = createTempPackageFile(mDrmAppPrivateInstallDir);
+                        if (mTempPackage != null) {
+                            try {
+                                out = ParcelFileDescriptor.open(mTempPackage,
+                                        ParcelFileDescriptor.MODE_READ_WRITE);
+                            } catch (FileNotFoundException e) {
+                                Slog.e(TAG, "Failed to create temporary file for : " + mPackageURI);
+                            }
+
+                            // Make a temporary file for decryption.
+                            ret = mContainerService
+                                    .copyResource(mPackageURI, encryptionParams, out);
+
+                            packageFile = mTempPackage;
+
+                            FileUtils.setPermissions(packageFile.getAbsolutePath(),
+                                    FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IROTH,
+                                    -1, -1);
+                        } else {
+                            packageFile = null;
+                        }
+                    } else {
+                        packageFile = new File(mPackageURI.getPath());
+                    }
+
+                    if (packageFile != null) {
+                        // Remote call to find out default install location
+                        pkgLite = mContainerService.getMinimalPackageInfo(
+                                packageFile.getAbsolutePath(), flags, lowThreshold);
+                    }
+                } finally {
+                    mContext.revokeUriPermission(mPackageURI,
+                            Intent.FLAG_GRANT_READ_URI_PERMISSION);
+                }
+            }
+
+            if (ret == PackageManager.INSTALL_SUCCEEDED) {
                 int loc = pkgLite.recommendedInstallLocation;
                 if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_LOCATION) {
                     ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
@@ -5708,8 +5744,9 @@
                 final int requiredUid = mRequiredVerifierPackage == null ? -1
                         : getPackageUid(mRequiredVerifierPackage, 0);
                 if (requiredUid != -1 && isVerificationEnabled()) {
-                    final Intent verification = new Intent(Intent.ACTION_PACKAGE_NEEDS_VERIFICATION);
-                    verification.setDataAndType(packageURI, PACKAGE_MIME_TYPE);
+                    final Intent verification = new Intent(
+                            Intent.ACTION_PACKAGE_NEEDS_VERIFICATION);
+                    verification.setDataAndType(getPackageUri(), PACKAGE_MIME_TYPE);
                     verification.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
 
                     final List<ResolveInfo> receivers = queryIntentReceivers(verification, null,
@@ -5812,6 +5849,13 @@
             if (mArgs != null) {
                 processPendingInstall(mArgs, mRet);
             }
+
+            if (mTempPackage != null) {
+                if (!mTempPackage.delete()) {
+                    Slog.w(TAG, "Couldn't delete temporary file: "
+                            + mTempPackage.getAbsolutePath());
+                }
+            }
         }
 
         @Override
@@ -5823,6 +5867,14 @@
         public boolean isForwardLocked() {
             return (flags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
         }
+
+        public Uri getPackageUri() {
+            if (mTempPackage != null) {
+                return Uri.fromFile(mTempPackage);
+            } else {
+                return mPackageURI;
+            }
+        }
     }
 
     /*
@@ -6037,8 +6089,8 @@
         boolean created = false;
 
         FileInstallArgs(InstallParams params) {
-            super(params.packageURI, params.observer, params.flags, params.installerPackageName,
-                    params.manifestDigest);
+            super(params.getPackageUri(), params.observer, params.flags,
+                    params.installerPackageName, params.manifestDigest);
         }
 
         FileInstallArgs(String fullCodePath, String fullResourcePath, String nativeLibraryPath) {
@@ -6128,7 +6180,7 @@
             try {
                 mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, packageURI,
                         Intent.FLAG_GRANT_READ_URI_PERMISSION);
-                ret = imcs.copyResource(packageURI, out);
+                ret = imcs.copyResource(packageURI, null, out);
             } finally {
                 IoUtils.closeQuietly(out);
                 mContext.revokeUriPermission(packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION);
@@ -6315,8 +6367,8 @@
         String libraryPath;
 
         AsecInstallArgs(InstallParams params) {
-            super(params.packageURI, params.observer, params.flags, params.installerPackageName,
-                    params.manifestDigest);
+            super(params.getPackageUri(), params.observer, params.flags,
+                    params.installerPackageName, params.manifestDigest);
         }
 
         AsecInstallArgs(String fullCodePath, String fullResourcePath, String nativeLibraryPath,
diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index 5610134..86689f3 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -22,6 +22,7 @@
 import android.content.IntentSender;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.ContainerEncryptionParams;
 import android.content.pm.FeatureInfo;
 import android.content.pm.IPackageDataObserver;
 import android.content.pm.IPackageDeleteObserver;
@@ -29,6 +30,7 @@
 import android.content.pm.IPackageMoveObserver;
 import android.content.pm.IPackageStatsObserver;
 import android.content.pm.InstrumentationInfo;
+import android.content.pm.ManifestDigest;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PermissionGroupInfo;
@@ -36,16 +38,12 @@
 import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
-import android.content.pm.Signature;
 import android.content.pm.UserInfo;
-import android.content.pm.ManifestDigest;
 import android.content.pm.VerifierDeviceIdentity;
-import android.content.pm.VerifierInfo;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
-import android.os.RemoteException;
 
 import java.util.List;
 
@@ -565,7 +563,7 @@
     @Override
     public void installPackageWithVerification(Uri packageURI, IPackageInstallObserver observer,
             int flags, String installerPackageName, Uri verificationURI,
-            ManifestDigest manifestDigest) {
+            ManifestDigest manifestDigest, ContainerEncryptionParams encryptionParams) {
         throw new UnsupportedOperationException();
     }