Initial support for split APKs, PackageInstaller.

Defines a new PackageInstaller class that will be used for installing
and upgrading packages.  An application desiring to install an
application creates a session, stages one or more package files in
that session, and then kicks off the install.

Previously, PackageManager would always make its own copy of a package
before inspecting it, to ensure the data could be trusted.  This new
session concept allows the installer to write package data directly to
its final resting place on disk, reducing disk I/O and footprint
requirements.  Writes are directed through an intermediate pipe
to ensure we can prevent mutations once an install has been initiated.
Also uses fallocate() internally to support optimal ext4 block
allocation using extents to reduce fragmentation.

Sessions are also the way we support installing multiple "split" APKs
in a single atomic operation.  For a set of packages to form a valid
application, they must have exactly the same package name, version
code, and certificates.  A session can also be used to add a small
handful of splits to an application by inheriting existing packages
when not performing a full install.

Add PackageParser support for extracting split names and certificates.

Bug: 14975160
Change-Id: I23d1bf4fbeb9f99a8c83be0c458900a0f0d1bccc
diff --git a/core/java/android/content/pm/ContainerEncryptionParams.java b/core/java/android/content/pm/ContainerEncryptionParams.java
index 18dcb56..dd1332b 100644
--- a/core/java/android/content/pm/ContainerEncryptionParams.java
+++ b/core/java/android/content/pm/ContainerEncryptionParams.java
@@ -32,9 +32,11 @@
 /**
  * Represents encryption parameters used to read a container.
  *
+ * @deprecated encrypted containers are legacy.
  * @hide
  */
 @PrivateApi
+@Deprecated
 public class ContainerEncryptionParams implements Parcelable {
     protected static final String TAG = "ContainerEncryptionParams";
 
diff --git a/core/java/android/content/pm/IPackageInstallObserver2.aidl b/core/java/android/content/pm/IPackageInstallObserver2.aidl
index 2602ab5..7205ce7 100644
--- a/core/java/android/content/pm/IPackageInstallObserver2.aidl
+++ b/core/java/android/content/pm/IPackageInstallObserver2.aidl
@@ -1,22 +1,22 @@
 /*
-**
-** Copyright 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.
-*/
+ * 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.IntentSender;
 import android.os.Bundle;
 
 /**
@@ -40,6 +40,5 @@
      * </tr>
      * </table>
      */
-    void packageInstalled(in String packageName, in Bundle extras, int returnCode);
+    void packageInstalled(String basePackageName, in Bundle extras, int returnCode);
 }
-
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
new file mode 100644
index 0000000..68c019b
--- /dev/null
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -0,0 +1,33 @@
+/*
+ * 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.IPackageDeleteObserver;
+import android.content.pm.IPackageInstallerSession;
+import android.content.pm.PackageInstallerParams;
+import android.os.ParcelFileDescriptor;
+
+/** {@hide} */
+interface IPackageInstaller {
+    int createSession(int userId, String installerPackageName, in PackageInstallerParams params);
+    IPackageInstallerSession openSession(int sessionId);
+
+    int[] getSessions(int userId, String installerPackageName);
+
+    void uninstall(int userId, String basePackageName, in IPackageDeleteObserver observer);
+    void uninstallSplit(int userId, String basePackageName, String splitName, in IPackageDeleteObserver observer);
+}
diff --git a/core/java/android/content/pm/IPackageInstallerSession.aidl b/core/java/android/content/pm/IPackageInstallerSession.aidl
new file mode 100644
index 0000000..f881acd
--- /dev/null
+++ b/core/java/android/content/pm/IPackageInstallerSession.aidl
@@ -0,0 +1,30 @@
+/*
+ * 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.IPackageInstallObserver2;
+import android.os.ParcelFileDescriptor;
+
+/** {@hide} */
+interface IPackageInstallerSession {
+    void updateProgress(int progress);
+
+    ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes);
+
+    void install(in IPackageInstallObserver2 observer);
+    void destroy();
+}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 03eb50f..6cb781f 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -26,6 +26,7 @@
 import android.content.pm.FeatureInfo;
 import android.content.pm.IPackageInstallObserver;
 import android.content.pm.IPackageInstallObserver2;
+import android.content.pm.IPackageInstaller;
 import android.content.pm.IPackageDeleteObserver;
 import android.content.pm.IPackageDataObserver;
 import android.content.pm.IPackageMoveObserver;
@@ -450,4 +451,6 @@
 
     boolean setApplicationBlockedSettingAsUser(String packageName, boolean blocked, int userId);
     boolean getApplicationBlockedSettingAsUser(String packageName, int userId);
+
+    IPackageInstaller getPackageInstaller();
 }
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
new file mode 100644
index 0000000..d7bd473
--- /dev/null
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -0,0 +1,159 @@
+/*
+ * 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.app.PackageInstallObserver;
+import android.app.PackageUninstallObserver;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+
+/** {@hide} */
+public class PackageInstaller {
+    private final PackageManager mPm;
+    private final IPackageInstaller mInstaller;
+    private final int mUserId;
+    private final String mInstallerPackageName;
+
+    /** {@hide} */
+    public PackageInstaller(PackageManager pm, IPackageInstaller installer, int userId,
+            String installerPackageName) {
+        mPm = pm;
+        mInstaller = installer;
+        mUserId = userId;
+        mInstallerPackageName = installerPackageName;
+    }
+
+    public boolean isPackageAvailable(String basePackageName) {
+        try {
+            final ApplicationInfo info = mPm.getApplicationInfo(basePackageName,
+                    PackageManager.GET_UNINSTALLED_PACKAGES);
+            return ((info.flags & ApplicationInfo.FLAG_INSTALLED) != 0);
+        } catch (NameNotFoundException e) {
+            return false;
+        }
+    }
+
+    public void installAvailablePackage(String basePackageName, PackageInstallObserver observer) {
+        int returnCode;
+        try {
+            returnCode = mPm.installExistingPackage(basePackageName);
+        } catch (NameNotFoundException e) {
+            returnCode = PackageManager.INSTALL_FAILED_PACKAGE_CHANGED;
+        }
+        observer.packageInstalled(basePackageName, null, returnCode);
+    }
+
+    public int createSession(PackageInstallerParams params) {
+        try {
+            return mInstaller.createSession(mUserId, mInstallerPackageName, params);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    public Session openSession(int sessionId) {
+        try {
+            return new Session(mInstaller.openSession(sessionId));
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    public int[] getSessions() {
+        try {
+            return mInstaller.getSessions(mUserId, mInstallerPackageName);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    public void uninstall(String basePackageName, PackageUninstallObserver observer) {
+        try {
+            mInstaller.uninstall(mUserId, basePackageName, observer.getBinder());
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    public void uninstall(String basePackageName, String splitName,
+            PackageUninstallObserver observer) {
+        try {
+            mInstaller.uninstallSplit(mUserId, basePackageName, splitName, observer.getBinder());
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
+     * An installation that is being actively staged. For an install to succeed,
+     * all existing and new packages must have identical package names, version
+     * codes, and signing certificates.
+     * <p>
+     * A session may contain any number of split packages. If the application
+     * does not yet exist, this session must include a base package.
+     * <p>
+     * If a package included in this session is already defined by the existing
+     * installation (for example, the same split name), the package in this
+     * session will replace the existing package.
+     */
+    public class Session {
+        private IPackageInstallerSession mSession;
+
+        /** {@hide} */
+        public Session(IPackageInstallerSession session) {
+            mSession = session;
+        }
+
+        public void updateProgress(int progress) {
+            try {
+                mSession.updateProgress(progress);
+            } catch (RemoteException e) {
+                throw e.rethrowAsRuntimeException();
+            }
+        }
+
+        public ParcelFileDescriptor openWrite(String overlayName, long offsetBytes,
+                long lengthBytes) {
+            try {
+                return mSession.openWrite(overlayName, offsetBytes, lengthBytes);
+            } catch (RemoteException e) {
+                throw e.rethrowAsRuntimeException();
+            }
+        }
+
+        public void install(PackageInstallObserver observer) {
+            try {
+                mSession.install(observer.getBinder());
+            } catch (RemoteException e) {
+                throw e.rethrowAsRuntimeException();
+            }
+        }
+
+        public void close() {
+            // No resources to release at the moment
+        }
+
+        public void destroy() {
+            try {
+                mSession.destroy();
+            } catch (RemoteException e) {
+                throw e.rethrowAsRuntimeException();
+            }
+        }
+    }
+}
diff --git a/core/java/android/content/pm/PackageInstallerParams.aidl b/core/java/android/content/pm/PackageInstallerParams.aidl
new file mode 100644
index 0000000..b3dde21
--- /dev/null
+++ b/core/java/android/content/pm/PackageInstallerParams.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 PackageInstallerParams;
diff --git a/core/java/android/content/pm/PackageInstallerParams.java b/core/java/android/content/pm/PackageInstallerParams.java
new file mode 100644
index 0000000..67cf276
--- /dev/null
+++ b/core/java/android/content/pm/PackageInstallerParams.java
@@ -0,0 +1,141 @@
+/*
+ * 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.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Parameters that define an installation session.
+ *
+ * {@hide}
+ */
+public class PackageInstallerParams implements Parcelable {
+
+    // TODO: extend to support remaining VerificationParams
+
+    /** {@hide} */
+    public boolean fullInstall;
+    /** {@hide} */
+    public int installFlags;
+    /** {@hide} */
+    public int installLocation = PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY;
+    /** {@hide} */
+    public Signature[] signatures;
+    /** {@hide} */
+    public long deltaSize = -1;
+    /** {@hide} */
+    public Bitmap icon;
+    /** {@hide} */
+    public String title;
+    /** {@hide} */
+    public Uri originatingUri;
+    /** {@hide} */
+    public Uri referrerUri;
+
+    public PackageInstallerParams() {
+    }
+
+    /** {@hide} */
+    public PackageInstallerParams(Parcel source) {
+        this.fullInstall = source.readInt() != 0;
+        this.installFlags = source.readInt();
+        this.installLocation = source.readInt();
+        this.signatures = (Signature[]) source.readParcelableArray(null);
+        deltaSize = source.readLong();
+        if (source.readInt() != 0) {
+            icon = Bitmap.CREATOR.createFromParcel(source);
+        }
+        title = source.readString();
+        originatingUri = Uri.CREATOR.createFromParcel(source);
+        referrerUri = Uri.CREATOR.createFromParcel(source);
+    }
+
+    public void setFullInstall(boolean fullInstall) {
+        this.fullInstall = fullInstall;
+    }
+
+    public void setInstallFlags(int installFlags) {
+        this.installFlags = installFlags;
+    }
+
+    public void setInstallLocation(int installLocation) {
+        this.installLocation = installLocation;
+    }
+
+    public void setSignatures(Signature[] signatures) {
+        this.signatures = signatures;
+    }
+
+    public void setDeltaSize(long deltaSize) {
+        this.deltaSize = deltaSize;
+    }
+
+    public void setIcon(Bitmap icon) {
+        this.icon = icon;
+    }
+
+    public void setTitle(CharSequence title) {
+        this.title = (title != null) ? title.toString() : null;
+    }
+
+    public void setOriginatingUri(Uri originatingUri) {
+        this.originatingUri = originatingUri;
+    }
+
+    public void setReferrerUri(Uri referrerUri) {
+        this.referrerUri = referrerUri;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(fullInstall ? 1 : 0);
+        dest.writeInt(installFlags);
+        dest.writeInt(installLocation);
+        dest.writeParcelableArray(signatures, flags);
+        dest.writeLong(deltaSize);
+        if (icon != null) {
+            dest.writeInt(1);
+            icon.writeToParcel(dest, flags);
+        } else {
+            dest.writeInt(0);
+        }
+        dest.writeString(title);
+        dest.writeParcelable(originatingUri, flags);
+        dest.writeParcelable(referrerUri, flags);
+    }
+
+    public static final Parcelable.Creator<PackageInstallerParams>
+            CREATOR = new Parcelable.Creator<PackageInstallerParams>() {
+                @Override
+                public PackageInstallerParams createFromParcel(Parcel p) {
+                    return new PackageInstallerParams(p);
+                }
+
+                @Override
+                public PackageInstallerParams[] newArray(int size) {
+                    return new PackageInstallerParams[size];
+                }
+            };
+}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 1bc1449..2aa34d8 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -3551,6 +3551,9 @@
      */
     public abstract VerifierDeviceIdentity getVerifierDeviceIdentity();
 
+    /** {@hide} */
+    public abstract PackageInstaller getPackageInstaller();
+
     /**
      * Returns the data directory for a particular user and package, given the uid of the package.
      * @param uid uid of the package, including the userId and appId
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index ff96c51..1c838c3 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -16,6 +16,9 @@
 
 package android.content.pm;
 
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -32,6 +35,7 @@
 import android.util.Base64;
 import android.util.DisplayMetrics;
 import android.util.Log;
+import android.util.Pair;
 import android.util.Slog;
 import android.util.TypedValue;
 
@@ -41,6 +45,7 @@
 import java.io.InputStream;
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
+import java.security.GeneralSecurityException;
 import java.security.KeyFactory;
 import java.security.NoSuchAlgorithmException;
 import java.security.PublicKey;
@@ -51,7 +56,6 @@
 import java.security.spec.X509EncodedKeySpec;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -61,6 +65,7 @@
 import java.util.jar.StrictJarFile;
 import java.util.zip.ZipEntry;
 
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.XmlUtils;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -207,16 +212,20 @@
      */
     public static class PackageLite {
         public final String packageName;
+        public final String splitName;
         public final int versionCode;
         public final int installLocation;
         public final VerifierInfo[] verifiers;
+        public final Signature[] signatures;
 
-        public PackageLite(String packageName, int versionCode,
-                int installLocation, List<VerifierInfo> verifiers) {
+        public PackageLite(String packageName, String splitName, int versionCode,
+                int installLocation, List<VerifierInfo> verifiers, Signature[] signatures) {
             this.packageName = packageName;
+            this.splitName = splitName;
             this.versionCode = versionCode;
             this.installLocation = installLocation;
             this.verifiers = verifiers.toArray(new VerifierInfo[verifiers.size()]);
+            this.signatures = signatures;
         }
     }
 
@@ -459,7 +468,7 @@
         return pi;
     }
 
-    private Certificate[][] loadCertificates(StrictJarFile jarFile, ZipEntry je,
+    private static Certificate[][] loadCertificates(StrictJarFile jarFile, ZipEntry je,
             byte[] readBuffer) {
         try {
             // We must read the stream for the JarEntry to retrieve
@@ -486,6 +495,7 @@
     public final static int PARSE_ON_SDCARD = 1<<5;
     public final static int PARSE_IS_SYSTEM_DIR = 1<<6;
     public final static int PARSE_IS_PRIVILEGED = 1<<7;
+    public final static int PARSE_GET_SIGNATURES = 1<<8;
 
     public int getParseError() {
         return mParseError;
@@ -722,12 +732,8 @@
                 mReadBuffer = readBufferRef;
             }
 
-            if (certs != null && certs.length > 0) {
-                final int N = certs.length;
-                pkg.mSignatures = new Signature[certs.length];
-                for (int i=0; i<N; i++) {
-                    pkg.mSignatures[i] = new Signature(certs[i]);
-                }
+            if (!ArrayUtils.isEmpty(certs)) {
+                pkg.mSignatures = convertToSignatures(certs);
             } else {
                 Slog.e(TAG, "Package " + pkg.packageName
                         + " has no certificates; ignoring!");
@@ -762,6 +768,39 @@
         return true;
     }
 
+    /**
+     * Only collect certificates on the manifest; does not validate signatures
+     * across remainder of package.
+     */
+    private static Signature[] collectCertificates(String packageFilePath) {
+        try {
+            final StrictJarFile jarFile = new StrictJarFile(packageFilePath);
+            try {
+                final ZipEntry jarEntry = jarFile.findEntry(ANDROID_MANIFEST_FILENAME);
+                if (jarEntry != null) {
+                    final Certificate[][] certs = loadCertificates(jarFile, jarEntry, null);
+                    return convertToSignatures(certs);
+                }
+            } finally {
+                jarFile.close();
+            }
+        } catch (GeneralSecurityException e) {
+            Slog.w(TAG, "Failed to collect certs from " + packageFilePath + ": " + e);
+        } catch (IOException e) {
+            Slog.w(TAG, "Failed to collect certs from " + packageFilePath + ": " + e);
+        }
+        return null;
+    }
+
+    private static Signature[] convertToSignatures(Certificate[][] certs)
+            throws CertificateEncodingException {
+        final Signature[] res = new Signature[certs.length];
+        for (int i = 0; i < certs.length; i++) {
+            res[i] = new Signature(certs[i]);
+        }
+        return res;
+    }
+
     /*
      * Utility method that retrieves just the package name and install
      * location from the apk location at the given file path.
@@ -794,11 +833,22 @@
             return null;
         }
 
+        // Only collect certificates on the manifest; does not validate
+        // signatures across remainder of package.
+        final Signature[] signatures;
+        if ((flags & PARSE_GET_SIGNATURES) != 0) {
+            signatures = collectCertificates(packageFilePath);
+        } else {
+            signatures = null;
+        }
+
         final AttributeSet attrs = parser;
         final String errors[] = new String[1];
         PackageLite packageLite = null;
         try {
-            packageLite = parsePackageLite(res, parser, attrs, flags, errors);
+            packageLite = parsePackageLite(res, parser, attrs, flags, signatures, errors);
+        } catch (PackageParserException e) {
+            Slog.w(TAG, packageFilePath, e);
         } catch (IOException e) {
             Slog.w(TAG, packageFilePath, e);
         } catch (XmlPullParserException e) {
@@ -840,72 +890,51 @@
                 ? null : "must have at least one '.' separator";
     }
 
-    private static String parsePackageName(XmlPullParser parser,
-            AttributeSet attrs, int flags, String[] outError)
-            throws IOException, XmlPullParserException {
+    private static Pair<String, String> parsePackageSplitNames(XmlPullParser parser,
+            AttributeSet attrs, int flags) throws IOException, XmlPullParserException,
+            PackageParserException {
 
         int type;
         while ((type = parser.next()) != XmlPullParser.START_TAG
                 && type != XmlPullParser.END_DOCUMENT) {
-            ;
         }
 
         if (type != XmlPullParser.START_TAG) {
-            outError[0] = "No start tag found";
-            return null;
+            throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+                    "No start tag found");
         }
-        if (DEBUG_PARSER)
-            Slog.v(TAG, "Root element name: '" + parser.getName() + "'");
         if (!parser.getName().equals("manifest")) {
-            outError[0] = "No <manifest> tag";
-            return null;
-        }
-        String pkgName = attrs.getAttributeValue(null, "package");
-        if (pkgName == null || pkgName.length() == 0) {
-            outError[0] = "<manifest> does not specify package";
-            return null;
-        }
-        String nameError = validateName(pkgName, true);
-        if (nameError != null && !"android".equals(pkgName)) {
-            outError[0] = "<manifest> specifies bad package name \""
-                + pkgName + "\": " + nameError;
-            return null;
+            throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+                    "No <manifest> tag");
         }
 
-        return pkgName.intern();
+        final String packageName = attrs.getAttributeValue(null, "package");
+        if (!"android".equals(packageName)) {
+            final String error = validateName(packageName, true);
+            if (error != null) {
+                throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME,
+                        "Invalid manifest package: " + error);
+            }
+        }
+
+        final String splitName = attrs.getAttributeValue(null, "split");
+        if (splitName != null) {
+            final String error = validateName(splitName, true);
+            if (error != null) {
+                throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME,
+                        "Invalid manifest split: " + error);
+            }
+        }
+
+        return Pair.create(packageName.intern(),
+                (splitName != null) ? splitName.intern() : splitName);
     }
 
     private static PackageLite parsePackageLite(Resources res, XmlPullParser parser,
-            AttributeSet attrs, int flags, String[] outError) throws IOException,
-            XmlPullParserException {
+            AttributeSet attrs, int flags, Signature[] signatures, String[] outError)
+            throws IOException, XmlPullParserException, PackageParserException {
+        final Pair<String, String> packageSplit = parsePackageSplitNames(parser, attrs, flags);
 
-        int type;
-        while ((type = parser.next()) != XmlPullParser.START_TAG
-                && type != XmlPullParser.END_DOCUMENT) {
-            ;
-        }
-
-        if (type != XmlPullParser.START_TAG) {
-            outError[0] = "No start tag found";
-            return null;
-        }
-        if (DEBUG_PARSER)
-            Slog.v(TAG, "Root element name: '" + parser.getName() + "'");
-        if (!parser.getName().equals("manifest")) {
-            outError[0] = "No <manifest> tag";
-            return null;
-        }
-        String pkgName = attrs.getAttributeValue(null, "package");
-        if (pkgName == null || pkgName.length() == 0) {
-            outError[0] = "<manifest> does not specify package";
-            return null;
-        }
-        String nameError = validateName(pkgName, true);
-        if (nameError != null && !"android".equals(pkgName)) {
-            outError[0] = "<manifest> specifies bad package name \""
-                + pkgName + "\": " + nameError;
-            return null;
-        }
         int installLocation = PARSE_DEFAULT_INSTALL_LOCATION;
         int versionCode = 0;
         int numFound = 0;
@@ -925,6 +954,7 @@
         }
 
         // Only search the tree when the tag is directly below <manifest>
+        int type;
         final int searchDepth = parser.getDepth() + 1;
 
         final List<VerifierInfo> verifiers = new ArrayList<VerifierInfo>();
@@ -942,7 +972,8 @@
             }
         }
 
-        return new PackageLite(pkgName.intern(), versionCode, installLocation, verifiers);
+        return new PackageLite(packageSplit.first, packageSplit.second, versionCode,
+                installLocation, verifiers, signatures);
     }
 
     /**
@@ -966,12 +997,18 @@
         mParseActivityArgs = null;
         mParseServiceArgs = null;
         mParseProviderArgs = null;
-        
-        String pkgName = parsePackageName(parser, attrs, flags, outError);
-        if (pkgName == null) {
+
+        final String pkgName;
+        final String splitName;
+        try {
+            Pair<String, String> packageSplit = parsePackageSplitNames(parser, attrs, flags);
+            pkgName = packageSplit.first;
+            splitName = packageSplit.second;
+        } catch (PackageParserException e) {
             mParseError = PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME;
             return null;
         }
+
         int type;
 
         if (mOnlyCoreApps) {
@@ -982,9 +1019,9 @@
             }
         }
 
-        final Package pkg = new Package(pkgName);
+        final Package pkg = new Package(pkgName, splitName);
         boolean foundApp = false;
-        
+
         TypedArray sa = res.obtainAttributes(attrs,
                 com.android.internal.R.styleable.AndroidManifest);
         pkg.mVersionCode = pkg.applicationInfo.versionCode = sa.getInteger(
@@ -3544,6 +3581,7 @@
     public final static class Package {
 
         public String packageName;
+        public String splitName;
 
         // For now we only support one application per package.
         public final ApplicationInfo applicationInfo = new ApplicationInfo();
@@ -3660,9 +3698,10 @@
         public Set<PublicKey> mSigningKeys;
         public Map<String, Set<PublicKey>> mKeySetMapping;
 
-        public Package(String _name) {
-            packageName = _name;
-            applicationInfo.packageName = _name;
+        public Package(String packageName, String splitName) {
+            this.packageName = packageName;
+            this.splitName = splitName;
+            applicationInfo.packageName = packageName;
             applicationInfo.uid = -1;
         }
 
@@ -4267,4 +4306,13 @@
     public static void setCompatibilityModeEnabled(boolean compatibilityModeEnabled) {
         sCompatibilityModeEnabled = compatibilityModeEnabled;
     }
+
+    public static class PackageParserException extends Exception {
+        public final int error;
+
+        public PackageParserException(int error, String detailMessage) {
+            super(detailMessage);
+            this.error = error;
+        }
+    }
 }
diff --git a/core/java/android/content/pm/Signature.java b/core/java/android/content/pm/Signature.java
index f4e7dc3..96aa083 100644
--- a/core/java/android/content/pm/Signature.java
+++ b/core/java/android/content/pm/Signature.java
@@ -31,8 +31,10 @@
 import java.util.Arrays;
 
 /**
- * Opaque, immutable representation of a signature associated with an
+ * Opaque, immutable representation of a signing certificate associated with an
  * application package.
+ * <p>
+ * This class name is slightly misleading, since it's not actually a signature.
  */
 public class Signature implements Parcelable {
     private final byte[] mSignature;
diff --git a/core/java/android/content/pm/VerificationParams.java b/core/java/android/content/pm/VerificationParams.java
index 22e1a85..bf1f77f 100644
--- a/core/java/android/content/pm/VerificationParams.java
+++ b/core/java/android/content/pm/VerificationParams.java
@@ -24,8 +24,10 @@
 /**
  * Represents verification parameters used to verify packages to be installed.
  *
+ * @deprecated callers should migrate to {@link PackageInstallerParams}.
  * @hide
  */
+@Deprecated
 public class VerificationParams implements Parcelable {
     /** A constant used to indicate that a uid value is not present. */
     public static final int NO_UID = -1;