Merge "[Wifi][Passpoint] OSU Server status and failure callbacks"
diff --git a/Android.bp b/Android.bp
index 2dcbc92..00f42d2 100644
--- a/Android.bp
+++ b/Android.bp
@@ -256,6 +256,7 @@
         "core/java/android/service/euicc/IGetEidCallback.aidl",
         "core/java/android/service/euicc/IGetEuiccInfoCallback.aidl",
         "core/java/android/service/euicc/IGetEuiccProfileInfoListCallback.aidl",
+        "core/java/android/service/euicc/IGetOtaStatusCallback.aidl",
         "core/java/android/service/euicc/IRetainSubscriptionsForFactoryResetCallback.aidl",
         "core/java/android/service/euicc/ISwitchToSubscriptionCallback.aidl",
         "core/java/android/service/euicc/IUpdateSubscriptionNicknameCallback.aidl",
@@ -721,6 +722,7 @@
     ],
 
     srcs: [
+        "core/proto/android/os/batterytype.proto",
         "core/proto/android/os/cpufreq.proto",
         "core/proto/android/os/cpuinfo.proto",
         "core/proto/android/os/kernelwake.proto",
diff --git a/cmds/incident_helper/src/main.cpp b/cmds/incident_helper/src/main.cpp
index c8a0883..ab92473 100644
--- a/cmds/incident_helper/src/main.cpp
+++ b/cmds/incident_helper/src/main.cpp
@@ -16,6 +16,7 @@
 
 #define LOG_TAG "incident_helper"
 
+#include "parsers/BatteryTypeParser.h"
 #include "parsers/CpuFreqParser.h"
 #include "parsers/CpuInfoParser.h"
 #include "parsers/KernelWakesParser.h"
@@ -63,6 +64,8 @@
             return new CpuInfoParser();
         case 2004:
             return new CpuFreqParser();
+        case 2006:
+            return new BatteryTypeParser();
         default:
             return NULL;
     }
diff --git a/cmds/incident_helper/src/parsers/BatteryTypeParser.cpp b/cmds/incident_helper/src/parsers/BatteryTypeParser.cpp
new file mode 100644
index 0000000..ced6cf8
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/BatteryTypeParser.cpp
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#define LOG_TAG "incident_helper"
+
+#include <android/util/ProtoOutputStream.h>
+
+#include "frameworks/base/core/proto/android/os/batterytype.proto.h"
+#include "ih_util.h"
+#include "BatteryTypeParser.h"
+
+using namespace android::os;
+
+status_t
+BatteryTypeParser::Parse(const int in, const int out) const
+{
+    Reader reader(in);
+    string line;
+    bool readLine = false;
+
+    ProtoOutputStream proto;
+
+    // parse line by line
+    while (reader.readLine(&line)) {
+        if (line.empty()) continue;
+
+        if (readLine) {
+            fprintf(stderr, "Multiple lines in file. Unsure what to do.\n");
+            break;
+        }
+
+        proto.write(BatteryTypeProto::TYPE, line);
+
+        readLine = true;
+    }
+
+    if (!reader.ok(&line)) {
+        fprintf(stderr, "Bad read from fd %d: %s\n", in, line.c_str());
+        return -1;
+    }
+
+    if (!proto.flush(out)) {
+        fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
+        return -1;
+    }
+    fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.string(), proto.size());
+    return NO_ERROR;
+}
diff --git a/cmds/incident_helper/src/parsers/BatteryTypeParser.h b/cmds/incident_helper/src/parsers/BatteryTypeParser.h
new file mode 100644
index 0000000..ac0c098
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/BatteryTypeParser.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef BATTERY_TYPE_PARSER_H
+#define BATTERY_TYPE_PARSER_H
+
+#include "TextParserBase.h"
+
+using namespace android;
+
+/**
+ * Battery type parser, parses text in file
+ * /sys/class/power_supply/bms/battery_type.
+ */
+class BatteryTypeParser : public TextParserBase {
+public:
+    BatteryTypeParser() : TextParserBase(String8("BatteryTypeParser")) {};
+    ~BatteryTypeParser() {};
+
+    virtual status_t Parse(const int in, const int out) const;
+};
+
+#endif  // BATTERY_TYPE_PARSER_H
diff --git a/cmds/incident_helper/testdata/batterytype.txt b/cmds/incident_helper/testdata/batterytype.txt
new file mode 100644
index 0000000..c763d36
--- /dev/null
+++ b/cmds/incident_helper/testdata/batterytype.txt
@@ -0,0 +1 @@
+random_battery_type_string
diff --git a/cmds/incident_helper/tests/BatteryTypeParser_test.cpp b/cmds/incident_helper/tests/BatteryTypeParser_test.cpp
new file mode 100644
index 0000000..7fbe22d
--- /dev/null
+++ b/cmds/incident_helper/tests/BatteryTypeParser_test.cpp
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "BatteryTypeParser.h"
+
+#include "frameworks/base/core/proto/android/os/batterytype.pb.h"
+
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+#include <gmock/gmock.h>
+#include <google/protobuf/message_lite.h>
+#include <gtest/gtest.h>
+#include <string.h>
+#include <fcntl.h>
+
+using namespace android::base;
+using namespace android::os;
+using namespace std;
+using ::testing::StrEq;
+using ::testing::Test;
+using ::testing::internal::CaptureStderr;
+using ::testing::internal::CaptureStdout;
+using ::testing::internal::GetCapturedStderr;
+using ::testing::internal::GetCapturedStdout;
+
+class BatteryTypeParserTest : public Test {
+public:
+    virtual void SetUp() override {
+        ASSERT_TRUE(tf.fd != -1);
+    }
+
+protected:
+    TemporaryFile tf;
+
+    const string kTestPath = GetExecutableDirectory();
+    const string kTestDataPath = kTestPath + "/testdata/";
+};
+
+TEST_F(BatteryTypeParserTest, Success) {
+    const string testFile = kTestDataPath + "batterytype.txt";
+    BatteryTypeParser parser;
+    BatteryTypeProto expected;
+
+    expected.set_type("random_battery_type_string");
+
+    int fd = open(testFile.c_str(), O_RDONLY);
+    ASSERT_TRUE(fd != -1);
+
+    CaptureStdout();
+    ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
+    EXPECT_EQ(GetCapturedStdout(), expected.SerializeAsString());
+    close(fd);
+}
diff --git a/core/java/android/content/pm/PackageList.java b/core/java/android/content/pm/PackageList.java
new file mode 100644
index 0000000..cfd99ab
--- /dev/null
+++ b/core/java/android/content/pm/PackageList.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2017 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.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.PackageManagerInternal.PackageListObserver;
+
+import com.android.server.LocalServices;
+
+import java.util.List;
+
+/**
+ * All of the package name installed on the system.
+ * <p>A self observable list that automatically removes the listener when it goes out of scope.
+ *
+ * @hide Only for use within the system server.
+ */
+public class PackageList implements PackageListObserver, AutoCloseable {
+    private final PackageListObserver mWrappedObserver;
+    private final List<String> mPackageNames;
+
+    /**
+     * Create a new object.
+     * <p>Ownership of the given {@link List} transfers to this object and should not
+     * be modified by the caller.
+     */
+    public PackageList(@NonNull List<String> packageNames, @Nullable PackageListObserver observer) {
+        mPackageNames = packageNames;
+        mWrappedObserver = observer;
+    }
+
+    @Override
+    public void onPackageAdded(String packageName) {
+        if (mWrappedObserver != null) {
+            mWrappedObserver.onPackageAdded(packageName);
+        }
+    }
+
+    @Override
+    public void onPackageRemoved(String packageName) {
+        if (mWrappedObserver != null) {
+            mWrappedObserver.onPackageRemoved(packageName);
+        }
+    }
+
+    @Override
+    public void close() throws Exception {
+        LocalServices.getService(PackageManagerInternal.class).removePackageListObserver(this);
+    }
+
+    /**
+     * Returns the names of packages installed on the system.
+     * <p>The list is a copy-in-time and the actual set of installed packages may differ. Real
+     * time updates to the package list are sent via the {@link PackageListObserver} callback.
+     */
+    public @NonNull List<String> getPackageNames() {
+        return mPackageNames;
+    }
+}
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index 713cd10..8ee8e10 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -53,6 +53,14 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface KnownPackage {}
 
+    /** Observer called whenever the list of packages changes */
+    public interface PackageListObserver {
+        /** A package was added to the system. */
+        void onPackageAdded(@NonNull String packageName);
+        /** A package was removed from the system. */
+        void onPackageRemoved(@NonNull String packageName);
+    }
+
     /**
      * Provider for package names.
      */
@@ -435,6 +443,35 @@
     public abstract @Nullable PackageParser.Package getPackage(@NonNull String packageName);
 
     /**
+     * Returns a list without a change observer.
+     *
+     * {@see #getPackageList(PackageListObserver)}
+     */
+    public @NonNull PackageList getPackageList() {
+        return getPackageList(null);
+    }
+
+    /**
+     * Returns the list of packages installed at the time of the method call.
+     * <p>The given observer is notified when the list of installed packages
+     * changes [eg. a package was installed or uninstalled]. It will not be
+     * notified if a package is updated.
+     * <p>The package list will not be updated automatically as packages are
+     * installed / uninstalled. Any changes must be handled within the observer.
+     */
+    public abstract @NonNull PackageList getPackageList(@Nullable PackageListObserver observer);
+
+    /**
+     * Removes the observer.
+     * <p>Generally not needed. {@link #getPackageList(PackageListObserver)} will automatically
+     * remove the observer.
+     * <p>Does nothing if the observer isn't currently registered.
+     * <p>Observers are notified asynchronously and it's possible for an observer to be
+     * invoked after its been removed.
+     */
+    public abstract void removePackageListObserver(@NonNull PackageListObserver observer);
+
+    /**
      * Returns a package object for the disabled system package name.
      */
     public abstract @Nullable PackageParser.Package getDisabledPackage(@NonNull String packageName);
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 9833fe1..4c587a8 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -1115,12 +1115,14 @@
     /** {@hide} */
     public static Pair<String, Long> getPrimaryStoragePathAndSize() {
         return Pair.create(null,
-                FileUtils.roundStorageSize(Environment.getDataDirectory().getTotalSpace()));
+                FileUtils.roundStorageSize(Environment.getDataDirectory().getTotalSpace()
+                    + Environment.getRootDirectory().getTotalSpace()));
     }
 
     /** {@hide} */
     public long getPrimaryStorageSize() {
-        return FileUtils.roundStorageSize(Environment.getDataDirectory().getTotalSpace());
+        return FileUtils.roundStorageSize(Environment.getDataDirectory().getTotalSpace()
+                + Environment.getRootDirectory().getTotalSpace());
     }
 
     /** {@hide} */
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index acac671..2f86514 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5778,6 +5778,14 @@
             "touch_exploration_granted_accessibility_services";
 
         /**
+         * Uri of the slice that's presented on the keyguard.
+         * Defaults to a slice with the date and next alarm.
+         *
+         * @hide
+         */
+        public static final String KEYGUARD_SLICE_URI = "keyguard_slice_uri";
+
+        /**
          * Whether to speak passwords while in accessibility mode.
          *
          * @deprecated The speaking of passwords is controlled by individual accessibility services.
diff --git a/core/java/android/service/euicc/EuiccService.java b/core/java/android/service/euicc/EuiccService.java
index df0842f..fb53007 100644
--- a/core/java/android/service/euicc/EuiccService.java
+++ b/core/java/android/service/euicc/EuiccService.java
@@ -23,6 +23,7 @@
 import android.os.RemoteException;
 import android.telephony.euicc.DownloadableSubscription;
 import android.telephony.euicc.EuiccInfo;
+import android.telephony.euicc.EuiccManager.OtaStatus;
 import android.util.ArraySet;
 
 import java.util.concurrent.LinkedBlockingQueue;
@@ -203,6 +204,16 @@
     public abstract String onGetEid(int slotId);
 
     /**
+     * Return the status of OTA update.
+     *
+     * @param slotId ID of the SIM slot to use for the operation. This is currently not populated
+     *     but is here to future-proof the APIs.
+     * @return The status of Euicc OTA update.
+     * @see android.telephony.euicc.EuiccManager#getOtaStatus
+     */
+    public abstract @OtaStatus int onGetOtaStatus(int slotId);
+
+    /**
      * Populate {@link DownloadableSubscription} metadata for the given downloadable subscription.
      *
      * @param slotId ID of the SIM slot to use for the operation. This is currently not populated
@@ -385,6 +396,21 @@
         }
 
         @Override
+        public void getOtaStatus(int slotId, IGetOtaStatusCallback callback) {
+            mExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    int status = EuiccService.this.onGetOtaStatus(slotId);
+                    try {
+                        callback.onSuccess(status);
+                    } catch (RemoteException e) {
+                        // Can't communicate with the phone process; ignore.
+                    }
+                }
+            });
+        }
+
+        @Override
         public void getDownloadableSubscriptionMetadata(int slotId,
                 DownloadableSubscription subscription,
                 boolean forceDeactivateSim,
diff --git a/core/java/android/service/euicc/IEuiccService.aidl b/core/java/android/service/euicc/IEuiccService.aidl
index e10dd8c..a24e5c3 100644
--- a/core/java/android/service/euicc/IEuiccService.aidl
+++ b/core/java/android/service/euicc/IEuiccService.aidl
@@ -24,6 +24,7 @@
 import android.service.euicc.IGetEidCallback;
 import android.service.euicc.IGetEuiccInfoCallback;
 import android.service.euicc.IGetEuiccProfileInfoListCallback;
+import android.service.euicc.IGetOtaStatusCallback;
 import android.service.euicc.IRetainSubscriptionsForFactoryResetCallback;
 import android.service.euicc.ISwitchToSubscriptionCallback;
 import android.service.euicc.IUpdateSubscriptionNicknameCallback;
@@ -37,6 +38,7 @@
     void getDownloadableSubscriptionMetadata(int slotId, in DownloadableSubscription subscription,
             boolean forceDeactivateSim, in IGetDownloadableSubscriptionMetadataCallback callback);
     void getEid(int slotId, in IGetEidCallback callback);
+    void getOtaStatus(int slotId, in IGetOtaStatusCallback callback);
     void getEuiccProfileInfoList(int slotId, in IGetEuiccProfileInfoListCallback callback);
     void getDefaultDownloadableSubscriptionList(int slotId, boolean forceDeactivateSim,
             in IGetDefaultDownloadableSubscriptionListCallback callback);
diff --git a/core/java/android/service/euicc/IGetOtaStatusCallback.aidl b/core/java/android/service/euicc/IGetOtaStatusCallback.aidl
new file mode 100644
index 0000000..f667888
--- /dev/null
+++ b/core/java/android/service/euicc/IGetOtaStatusCallback.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2017 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.service.euicc;
+
+/** @hide */
+oneway interface IGetOtaStatusCallback {
+    void onSuccess(int status);
+}
\ No newline at end of file
diff --git a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
index 0a54f3a..530937e 100644
--- a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
+++ b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
@@ -16,6 +16,21 @@
 
 package android.util.apk;
 
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_DSA_WITH_SHA256;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_ECDSA_WITH_SHA256;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_ECDSA_WITH_SHA512;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PSS_WITH_SHA256;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PSS_WITH_SHA512;
+import static android.util.apk.ApkSigningBlockUtils.compareSignatureAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.getContentDigestAlgorithmJcaDigestAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.getLengthPrefixedSlice;
+import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmContentDigestAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaKeyAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.readLengthPrefixedByteArray;
+
 import android.util.ArrayMap;
 import android.util.Pair;
 
@@ -23,56 +38,47 @@
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.RandomAccessFile;
-import java.math.BigInteger;
 import java.nio.BufferUnderflowException;
 import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.security.DigestException;
 import java.security.InvalidAlgorithmParameterException;
 import java.security.InvalidKeyException;
 import java.security.KeyFactory;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.Principal;
 import java.security.PublicKey;
 import java.security.Signature;
 import java.security.SignatureException;
-import java.security.cert.CertificateEncodingException;
 import java.security.cert.CertificateException;
-import java.security.cert.CertificateExpiredException;
 import java.security.cert.CertificateFactory;
-import java.security.cert.CertificateNotYetValidException;
 import java.security.cert.X509Certificate;
 import java.security.spec.AlgorithmParameterSpec;
 import java.security.spec.InvalidKeySpecException;
-import java.security.spec.MGF1ParameterSpec;
-import java.security.spec.PSSParameterSpec;
 import java.security.spec.X509EncodedKeySpec;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Date;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 
 /**
  * APK Signature Scheme v2 verifier.
  *
+ * <p>APK Signature Scheme v2 is a whole-file signature scheme which aims to protect every single
+ * bit of the APK, as opposed to the JAR Signature Scheme which protects only the names and
+ * uncompressed contents of ZIP entries.
+ *
+ * @see <a href="https://source.android.com/security/apksigning/v2.html">APK Signature Scheme v2</a>
+ *
  * @hide for internal use only.
  */
 public class ApkSignatureSchemeV2Verifier {
 
     /**
-     * {@code .SF} file header section attribute indicating that the APK is signed not just with
-     * JAR signature scheme but also with APK Signature Scheme v2 or newer. This attribute
-     * facilitates v2 signature stripping detection.
-     *
-     * <p>The attribute contains a comma-separated set of signature scheme IDs.
+     * ID of this signature scheme as used in X-Android-APK-Signed header used in JAR signing.
      */
-    public static final String SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME = "X-Android-APK-Signed";
     public static final int SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID = 2;
 
+    private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a;
+
     /**
      * Returns {@code true} if the provided APK contains an APK Signature Scheme V2 signature.
      *
@@ -103,7 +109,7 @@
     /**
      * Returns the certificates associated with each signer for the given APK without verification.
      * This method is dangerous and should not be used, unless the caller is absolutely certain the
-     * APK is trusted.  Specifically, verification is only done for the APK Signature Scheme V2
+     * APK is trusted.  Specifically, verification is only done for the APK Signature Scheme v2
      * Block while gathering signer information.  The APK contents are not verified.
      *
      * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2.
@@ -120,6 +126,7 @@
             return verify(apk, verifyIntegrity);
         }
     }
+
     /**
      * Verifies APK Signature Scheme v2 signatures of the provided APK and returns the certificates
      * associated with each signer.
@@ -144,30 +151,7 @@
      */
     private static SignatureInfo findSignature(RandomAccessFile apk)
             throws IOException, SignatureNotFoundException {
-        // Find the ZIP End of Central Directory (EoCD) record.
-        Pair<ByteBuffer, Long> eocdAndOffsetInFile = getEocd(apk);
-        ByteBuffer eocd = eocdAndOffsetInFile.first;
-        long eocdOffset = eocdAndOffsetInFile.second;
-        if (ZipUtils.isZip64EndOfCentralDirectoryLocatorPresent(apk, eocdOffset)) {
-            throw new SignatureNotFoundException("ZIP64 APK not supported");
-        }
-
-        // Find the APK Signing Block. The block immediately precedes the Central Directory.
-        long centralDirOffset = getCentralDirOffset(eocd, eocdOffset);
-        Pair<ByteBuffer, Long> apkSigningBlockAndOffsetInFile =
-                findApkSigningBlock(apk, centralDirOffset);
-        ByteBuffer apkSigningBlock = apkSigningBlockAndOffsetInFile.first;
-        long apkSigningBlockOffset = apkSigningBlockAndOffsetInFile.second;
-
-        // Find the APK Signature Scheme v2 Block inside the APK Signing Block.
-        ByteBuffer apkSignatureSchemeV2Block = findApkSignatureSchemeV2Block(apkSigningBlock);
-
-        return new SignatureInfo(
-                apkSignatureSchemeV2Block,
-                apkSigningBlockOffset,
-                centralDirOffset,
-                eocdOffset,
-                eocd);
+        return ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V2_BLOCK_ID);
     }
 
     /**
@@ -218,7 +202,7 @@
         }
 
         if (doVerifyIntegrity) {
-            verifyIntegrity(
+            ApkSigningBlockUtils.verifyIntegrity(
                     contentDigests,
                     apkFileDescriptor,
                     signatureInfo.apkSigningBlockOffset,
@@ -349,7 +333,8 @@
             } catch (CertificateException e) {
                 throw new SecurityException("Failed to decode certificate #" + certificateCount, e);
             }
-            certificate = new VerbatimX509Certificate(certificate, encodedCert);
+            certificate = new VerbatimX509Certificate(
+                    certificate, encodedCert);
             certs.add(certificate);
         }
 
@@ -363,235 +348,44 @@
                     "Public key mismatch between certificate and signature record");
         }
 
+        ByteBuffer additionalAttrs = getLengthPrefixedSlice(signedData);
+        verifyAdditionalAttributes(additionalAttrs);
+
         return certs.toArray(new X509Certificate[certs.size()]);
     }
 
-    private static void verifyIntegrity(
-            Map<Integer, byte[]> expectedDigests,
-            FileDescriptor apkFileDescriptor,
-            long apkSigningBlockOffset,
-            long centralDirOffset,
-            long eocdOffset,
-            ByteBuffer eocdBuf) throws SecurityException {
+    // Attribute to check whether a newer APK Signature Scheme signature was stripped
+    private static final int STRIPPING_PROTECTION_ATTR_ID = 0xbeeff00d;
 
-        if (expectedDigests.isEmpty()) {
-            throw new SecurityException("No digests provided");
-        }
-
-        // We need to verify the integrity of the following three sections of the file:
-        // 1. Everything up to the start of the APK Signing Block.
-        // 2. ZIP Central Directory.
-        // 3. ZIP End of Central Directory (EoCD).
-        // Each of these sections is represented as a separate DataSource instance below.
-
-        // To handle large APKs, these sections are read in 1 MB chunks using memory-mapped I/O to
-        // avoid wasting physical memory. In most APK verification scenarios, the contents of the
-        // APK are already there in the OS's page cache and thus mmap does not use additional
-        // physical memory.
-        DataSource beforeApkSigningBlock =
-                new MemoryMappedFileDataSource(apkFileDescriptor, 0, apkSigningBlockOffset);
-        DataSource centralDir =
-                new MemoryMappedFileDataSource(
-                        apkFileDescriptor, centralDirOffset, eocdOffset - centralDirOffset);
-
-        // For the purposes of integrity verification, ZIP End of Central Directory's field Start of
-        // Central Directory must be considered to point to the offset of the APK Signing Block.
-        eocdBuf = eocdBuf.duplicate();
-        eocdBuf.order(ByteOrder.LITTLE_ENDIAN);
-        ZipUtils.setZipEocdCentralDirectoryOffset(eocdBuf, apkSigningBlockOffset);
-        DataSource eocd = new ByteBufferDataSource(eocdBuf);
-
-        int[] digestAlgorithms = new int[expectedDigests.size()];
-        int digestAlgorithmCount = 0;
-        for (int digestAlgorithm : expectedDigests.keySet()) {
-            digestAlgorithms[digestAlgorithmCount] = digestAlgorithm;
-            digestAlgorithmCount++;
-        }
-        byte[][] actualDigests;
-        try {
-            actualDigests =
-                    computeContentDigests(
-                            digestAlgorithms,
-                            new DataSource[] {beforeApkSigningBlock, centralDir, eocd});
-        } catch (DigestException e) {
-            throw new SecurityException("Failed to compute digest(s) of contents", e);
-        }
-        for (int i = 0; i < digestAlgorithms.length; i++) {
-            int digestAlgorithm = digestAlgorithms[i];
-            byte[] expectedDigest = expectedDigests.get(digestAlgorithm);
-            byte[] actualDigest = actualDigests[i];
-            if (!MessageDigest.isEqual(expectedDigest, actualDigest)) {
-                throw new SecurityException(
-                        getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm)
-                                + " digest of contents did not verify");
+    private static void verifyAdditionalAttributes(ByteBuffer attrs)
+            throws SecurityException, IOException {
+        while (attrs.hasRemaining()) {
+            ByteBuffer attr = getLengthPrefixedSlice(attrs);
+            if (attr.remaining() < 4) {
+                throw new IOException("Remaining buffer too short to contain additional attribute "
+                        + "ID. Remaining: " + attr.remaining());
             }
-        }
-    }
-
-    private static byte[][] computeContentDigests(
-            int[] digestAlgorithms,
-            DataSource[] contents) throws DigestException {
-        // For each digest algorithm the result is computed as follows:
-        // 1. Each segment of contents is split into consecutive chunks of 1 MB in size.
-        //    The final chunk will be shorter iff the length of segment is not a multiple of 1 MB.
-        //    No chunks are produced for empty (zero length) segments.
-        // 2. The digest of each chunk is computed over the concatenation of byte 0xa5, the chunk's
-        //    length in bytes (uint32 little-endian) and the chunk's contents.
-        // 3. The output digest is computed over the concatenation of the byte 0x5a, the number of
-        //    chunks (uint32 little-endian) and the concatenation of digests of chunks of all
-        //    segments in-order.
-
-        long totalChunkCountLong = 0;
-        for (DataSource input : contents) {
-            totalChunkCountLong += getChunkCount(input.size());
-        }
-        if (totalChunkCountLong >= Integer.MAX_VALUE / 1024) {
-            throw new DigestException("Too many chunks: " + totalChunkCountLong);
-        }
-        int totalChunkCount = (int) totalChunkCountLong;
-
-        byte[][] digestsOfChunks = new byte[digestAlgorithms.length][];
-        for (int i = 0; i < digestAlgorithms.length; i++) {
-            int digestAlgorithm = digestAlgorithms[i];
-            int digestOutputSizeBytes = getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm);
-            byte[] concatenationOfChunkCountAndChunkDigests =
-                    new byte[5 + totalChunkCount * digestOutputSizeBytes];
-            concatenationOfChunkCountAndChunkDigests[0] = 0x5a;
-            setUnsignedInt32LittleEndian(
-                    totalChunkCount,
-                    concatenationOfChunkCountAndChunkDigests,
-                    1);
-            digestsOfChunks[i] = concatenationOfChunkCountAndChunkDigests;
-        }
-
-        byte[] chunkContentPrefix = new byte[5];
-        chunkContentPrefix[0] = (byte) 0xa5;
-        int chunkIndex = 0;
-        MessageDigest[] mds = new MessageDigest[digestAlgorithms.length];
-        for (int i = 0; i < digestAlgorithms.length; i++) {
-            String jcaAlgorithmName =
-                    getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithms[i]);
-            try {
-                mds[i] = MessageDigest.getInstance(jcaAlgorithmName);
-            } catch (NoSuchAlgorithmException e) {
-                throw new RuntimeException(jcaAlgorithmName + " digest not supported", e);
-            }
-        }
-        // TODO: Compute digests of chunks in parallel when beneficial. This requires some research
-        // into how to parallelize (if at all) based on the capabilities of the hardware on which
-        // this code is running and based on the size of input.
-        DataDigester digester = new MultipleDigestDataDigester(mds);
-        int dataSourceIndex = 0;
-        for (DataSource input : contents) {
-            long inputOffset = 0;
-            long inputRemaining = input.size();
-            while (inputRemaining > 0) {
-                int chunkSize = (int) Math.min(inputRemaining, CHUNK_SIZE_BYTES);
-                setUnsignedInt32LittleEndian(chunkSize, chunkContentPrefix, 1);
-                for (int i = 0; i < mds.length; i++) {
-                    mds[i].update(chunkContentPrefix);
-                }
-                try {
-                    input.feedIntoDataDigester(digester, inputOffset, chunkSize);
-                } catch (IOException e) {
-                    throw new DigestException(
-                            "Failed to digest chunk #" + chunkIndex + " of section #"
-                                    + dataSourceIndex,
-                            e);
-                }
-                for (int i = 0; i < digestAlgorithms.length; i++) {
-                    int digestAlgorithm = digestAlgorithms[i];
-                    byte[] concatenationOfChunkCountAndChunkDigests = digestsOfChunks[i];
-                    int expectedDigestSizeBytes =
-                            getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm);
-                    MessageDigest md = mds[i];
-                    int actualDigestSizeBytes =
-                            md.digest(
-                                    concatenationOfChunkCountAndChunkDigests,
-                                    5 + chunkIndex * expectedDigestSizeBytes,
-                                    expectedDigestSizeBytes);
-                    if (actualDigestSizeBytes != expectedDigestSizeBytes) {
-                        throw new RuntimeException(
-                                "Unexpected output size of " + md.getAlgorithm() + " digest: "
-                                        + actualDigestSizeBytes);
+            int id = attr.getInt();
+            switch (id) {
+                case STRIPPING_PROTECTION_ATTR_ID:
+                    if (attr.remaining() < 4) {
+                        throw new IOException("V2 Signature Scheme Stripping Protection Attribute "
+                                + " value too small.  Expected 4 bytes, but found "
+                                + attr.remaining());
                     }
-                }
-                inputOffset += chunkSize;
-                inputRemaining -= chunkSize;
-                chunkIndex++;
+                    int vers = attr.getInt();
+                    if (vers == ApkSignatureSchemeV3Verifier.SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID) {
+                        throw new SecurityException("V2 signature indicates APK is signed using APK"
+                                + " Signature Scheme v3, but none was found. Signature stripped?");
+                    }
+                    break;
+                default:
+                    // not the droid we're looking for, move along, move along.
+                    break;
             }
-            dataSourceIndex++;
         }
-
-        byte[][] result = new byte[digestAlgorithms.length][];
-        for (int i = 0; i < digestAlgorithms.length; i++) {
-            int digestAlgorithm = digestAlgorithms[i];
-            byte[] input = digestsOfChunks[i];
-            String jcaAlgorithmName = getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm);
-            MessageDigest md;
-            try {
-                md = MessageDigest.getInstance(jcaAlgorithmName);
-            } catch (NoSuchAlgorithmException e) {
-                throw new RuntimeException(jcaAlgorithmName + " digest not supported", e);
-            }
-            byte[] output = md.digest(input);
-            result[i] = output;
-        }
-        return result;
+        return;
     }
-
-    /**
-     * Returns the ZIP End of Central Directory (EoCD) and its offset in the file.
-     *
-     * @throws IOException if an I/O error occurs while reading the file.
-     * @throws SignatureNotFoundException if the EoCD could not be found.
-     */
-    private static Pair<ByteBuffer, Long> getEocd(RandomAccessFile apk)
-            throws IOException, SignatureNotFoundException {
-        Pair<ByteBuffer, Long> eocdAndOffsetInFile =
-                ZipUtils.findZipEndOfCentralDirectoryRecord(apk);
-        if (eocdAndOffsetInFile == null) {
-            throw new SignatureNotFoundException(
-                    "Not an APK file: ZIP End of Central Directory record not found");
-        }
-        return eocdAndOffsetInFile;
-    }
-
-    private static long getCentralDirOffset(ByteBuffer eocd, long eocdOffset)
-            throws SignatureNotFoundException {
-        // Look up the offset of ZIP Central Directory.
-        long centralDirOffset = ZipUtils.getZipEocdCentralDirectoryOffset(eocd);
-        if (centralDirOffset > eocdOffset) {
-            throw new SignatureNotFoundException(
-                    "ZIP Central Directory offset out of range: " + centralDirOffset
-                    + ". ZIP End of Central Directory offset: " + eocdOffset);
-        }
-        long centralDirSize = ZipUtils.getZipEocdCentralDirectorySizeBytes(eocd);
-        if (centralDirOffset + centralDirSize != eocdOffset) {
-            throw new SignatureNotFoundException(
-                    "ZIP Central Directory is not immediately followed by End of Central"
-                    + " Directory");
-        }
-        return centralDirOffset;
-    }
-
-    private static final long getChunkCount(long inputSizeBytes) {
-        return (inputSizeBytes + CHUNK_SIZE_BYTES - 1) / CHUNK_SIZE_BYTES;
-    }
-
-    private static final int CHUNK_SIZE_BYTES = 1024 * 1024;
-
-    private static final int SIGNATURE_RSA_PSS_WITH_SHA256 = 0x0101;
-    private static final int SIGNATURE_RSA_PSS_WITH_SHA512 = 0x0102;
-    private static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256 = 0x0103;
-    private static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512 = 0x0104;
-    private static final int SIGNATURE_ECDSA_WITH_SHA256 = 0x0201;
-    private static final int SIGNATURE_ECDSA_WITH_SHA512 = 0x0202;
-    private static final int SIGNATURE_DSA_WITH_SHA256 = 0x0301;
-
-    private static final int CONTENT_DIGEST_CHUNKED_SHA256 = 1;
-    private static final int CONTENT_DIGEST_CHUNKED_SHA512 = 2;
-
     private static boolean isSupportedSignatureAlgorithm(int sigAlgorithm) {
         switch (sigAlgorithm) {
             case SIGNATURE_RSA_PSS_WITH_SHA256:
@@ -606,519 +400,4 @@
                 return false;
         }
     }
-
-    private static int compareSignatureAlgorithm(int sigAlgorithm1, int sigAlgorithm2) {
-        int digestAlgorithm1 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm1);
-        int digestAlgorithm2 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm2);
-        return compareContentDigestAlgorithm(digestAlgorithm1, digestAlgorithm2);
-    }
-
-    private static int compareContentDigestAlgorithm(int digestAlgorithm1, int digestAlgorithm2) {
-        switch (digestAlgorithm1) {
-            case CONTENT_DIGEST_CHUNKED_SHA256:
-                switch (digestAlgorithm2) {
-                    case CONTENT_DIGEST_CHUNKED_SHA256:
-                        return 0;
-                    case CONTENT_DIGEST_CHUNKED_SHA512:
-                        return -1;
-                    default:
-                        throw new IllegalArgumentException(
-                                "Unknown digestAlgorithm2: " + digestAlgorithm2);
-                }
-            case CONTENT_DIGEST_CHUNKED_SHA512:
-                switch (digestAlgorithm2) {
-                    case CONTENT_DIGEST_CHUNKED_SHA256:
-                        return 1;
-                    case CONTENT_DIGEST_CHUNKED_SHA512:
-                        return 0;
-                    default:
-                        throw new IllegalArgumentException(
-                                "Unknown digestAlgorithm2: " + digestAlgorithm2);
-                }
-            default:
-                throw new IllegalArgumentException("Unknown digestAlgorithm1: " + digestAlgorithm1);
-        }
-    }
-
-    private static int getSignatureAlgorithmContentDigestAlgorithm(int sigAlgorithm) {
-        switch (sigAlgorithm) {
-            case SIGNATURE_RSA_PSS_WITH_SHA256:
-            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
-            case SIGNATURE_ECDSA_WITH_SHA256:
-            case SIGNATURE_DSA_WITH_SHA256:
-                return CONTENT_DIGEST_CHUNKED_SHA256;
-            case SIGNATURE_RSA_PSS_WITH_SHA512:
-            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
-            case SIGNATURE_ECDSA_WITH_SHA512:
-                return CONTENT_DIGEST_CHUNKED_SHA512;
-            default:
-                throw new IllegalArgumentException(
-                        "Unknown signature algorithm: 0x"
-                                + Long.toHexString(sigAlgorithm & 0xffffffff));
-        }
-    }
-
-    private static String getContentDigestAlgorithmJcaDigestAlgorithm(int digestAlgorithm) {
-        switch (digestAlgorithm) {
-            case CONTENT_DIGEST_CHUNKED_SHA256:
-                return "SHA-256";
-            case CONTENT_DIGEST_CHUNKED_SHA512:
-                return "SHA-512";
-            default:
-                throw new IllegalArgumentException(
-                        "Unknown content digest algorthm: " + digestAlgorithm);
-        }
-    }
-
-    private static int getContentDigestAlgorithmOutputSizeBytes(int digestAlgorithm) {
-        switch (digestAlgorithm) {
-            case CONTENT_DIGEST_CHUNKED_SHA256:
-                return 256 / 8;
-            case CONTENT_DIGEST_CHUNKED_SHA512:
-                return 512 / 8;
-            default:
-                throw new IllegalArgumentException(
-                        "Unknown content digest algorthm: " + digestAlgorithm);
-        }
-    }
-
-    private static String getSignatureAlgorithmJcaKeyAlgorithm(int sigAlgorithm) {
-        switch (sigAlgorithm) {
-            case SIGNATURE_RSA_PSS_WITH_SHA256:
-            case SIGNATURE_RSA_PSS_WITH_SHA512:
-            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
-            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
-                return "RSA";
-            case SIGNATURE_ECDSA_WITH_SHA256:
-            case SIGNATURE_ECDSA_WITH_SHA512:
-                return "EC";
-            case SIGNATURE_DSA_WITH_SHA256:
-                return "DSA";
-            default:
-                throw new IllegalArgumentException(
-                        "Unknown signature algorithm: 0x"
-                                + Long.toHexString(sigAlgorithm & 0xffffffff));
-        }
-    }
-
-    private static Pair<String, ? extends AlgorithmParameterSpec>
-            getSignatureAlgorithmJcaSignatureAlgorithm(int sigAlgorithm) {
-        switch (sigAlgorithm) {
-            case SIGNATURE_RSA_PSS_WITH_SHA256:
-                return Pair.create(
-                        "SHA256withRSA/PSS",
-                        new PSSParameterSpec(
-                                "SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 256 / 8, 1));
-            case SIGNATURE_RSA_PSS_WITH_SHA512:
-                return Pair.create(
-                        "SHA512withRSA/PSS",
-                        new PSSParameterSpec(
-                                "SHA-512", "MGF1", MGF1ParameterSpec.SHA512, 512 / 8, 1));
-            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
-                return Pair.create("SHA256withRSA", null);
-            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
-                return Pair.create("SHA512withRSA", null);
-            case SIGNATURE_ECDSA_WITH_SHA256:
-                return Pair.create("SHA256withECDSA", null);
-            case SIGNATURE_ECDSA_WITH_SHA512:
-                return Pair.create("SHA512withECDSA", null);
-            case SIGNATURE_DSA_WITH_SHA256:
-                return Pair.create("SHA256withDSA", null);
-            default:
-                throw new IllegalArgumentException(
-                        "Unknown signature algorithm: 0x"
-                                + Long.toHexString(sigAlgorithm & 0xffffffff));
-        }
-    }
-
-    /**
-     * Returns new byte buffer whose content is a shared subsequence of this buffer's content
-     * between the specified start (inclusive) and end (exclusive) positions. As opposed to
-     * {@link ByteBuffer#slice()}, the returned buffer's byte order is the same as the source
-     * buffer's byte order.
-     */
-    private static ByteBuffer sliceFromTo(ByteBuffer source, int start, int end) {
-        if (start < 0) {
-            throw new IllegalArgumentException("start: " + start);
-        }
-        if (end < start) {
-            throw new IllegalArgumentException("end < start: " + end + " < " + start);
-        }
-        int capacity = source.capacity();
-        if (end > source.capacity()) {
-            throw new IllegalArgumentException("end > capacity: " + end + " > " + capacity);
-        }
-        int originalLimit = source.limit();
-        int originalPosition = source.position();
-        try {
-            source.position(0);
-            source.limit(end);
-            source.position(start);
-            ByteBuffer result = source.slice();
-            result.order(source.order());
-            return result;
-        } finally {
-            source.position(0);
-            source.limit(originalLimit);
-            source.position(originalPosition);
-        }
-    }
-
-    /**
-     * Relative <em>get</em> method for reading {@code size} number of bytes from the current
-     * position of this buffer.
-     *
-     * <p>This method reads the next {@code size} bytes at this buffer's current position,
-     * returning them as a {@code ByteBuffer} with start set to 0, limit and capacity set to
-     * {@code size}, byte order set to this buffer's byte order; and then increments the position by
-     * {@code size}.
-     */
-    private static ByteBuffer getByteBuffer(ByteBuffer source, int size)
-            throws BufferUnderflowException {
-        if (size < 0) {
-            throw new IllegalArgumentException("size: " + size);
-        }
-        int originalLimit = source.limit();
-        int position = source.position();
-        int limit = position + size;
-        if ((limit < position) || (limit > originalLimit)) {
-            throw new BufferUnderflowException();
-        }
-        source.limit(limit);
-        try {
-            ByteBuffer result = source.slice();
-            result.order(source.order());
-            source.position(limit);
-            return result;
-        } finally {
-            source.limit(originalLimit);
-        }
-    }
-
-    private static ByteBuffer getLengthPrefixedSlice(ByteBuffer source) throws IOException {
-        if (source.remaining() < 4) {
-            throw new IOException(
-                    "Remaining buffer too short to contain length of length-prefixed field."
-                            + " Remaining: " + source.remaining());
-        }
-        int len = source.getInt();
-        if (len < 0) {
-            throw new IllegalArgumentException("Negative length");
-        } else if (len > source.remaining()) {
-            throw new IOException("Length-prefixed field longer than remaining buffer."
-                    + " Field length: " + len + ", remaining: " + source.remaining());
-        }
-        return getByteBuffer(source, len);
-    }
-
-    private static byte[] readLengthPrefixedByteArray(ByteBuffer buf) throws IOException {
-        int len = buf.getInt();
-        if (len < 0) {
-            throw new IOException("Negative length");
-        } else if (len > buf.remaining()) {
-            throw new IOException("Underflow while reading length-prefixed value. Length: " + len
-                    + ", available: " + buf.remaining());
-        }
-        byte[] result = new byte[len];
-        buf.get(result);
-        return result;
-    }
-
-    private static void setUnsignedInt32LittleEndian(int value, byte[] result, int offset) {
-        result[offset] = (byte) (value & 0xff);
-        result[offset + 1] = (byte) ((value >>> 8) & 0xff);
-        result[offset + 2] = (byte) ((value >>> 16) & 0xff);
-        result[offset + 3] = (byte) ((value >>> 24) & 0xff);
-    }
-
-    private static final long APK_SIG_BLOCK_MAGIC_HI = 0x3234206b636f6c42L;
-    private static final long APK_SIG_BLOCK_MAGIC_LO = 0x20676953204b5041L;
-    private static final int APK_SIG_BLOCK_MIN_SIZE = 32;
-
-    private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a;
-
-    private static Pair<ByteBuffer, Long> findApkSigningBlock(
-            RandomAccessFile apk, long centralDirOffset)
-                    throws IOException, SignatureNotFoundException {
-        // FORMAT:
-        // OFFSET       DATA TYPE  DESCRIPTION
-        // * @+0  bytes uint64:    size in bytes (excluding this field)
-        // * @+8  bytes payload
-        // * @-24 bytes uint64:    size in bytes (same as the one above)
-        // * @-16 bytes uint128:   magic
-
-        if (centralDirOffset < APK_SIG_BLOCK_MIN_SIZE) {
-            throw new SignatureNotFoundException(
-                    "APK too small for APK Signing Block. ZIP Central Directory offset: "
-                            + centralDirOffset);
-        }
-        // Read the magic and offset in file from the footer section of the block:
-        // * uint64:   size of block
-        // * 16 bytes: magic
-        ByteBuffer footer = ByteBuffer.allocate(24);
-        footer.order(ByteOrder.LITTLE_ENDIAN);
-        apk.seek(centralDirOffset - footer.capacity());
-        apk.readFully(footer.array(), footer.arrayOffset(), footer.capacity());
-        if ((footer.getLong(8) != APK_SIG_BLOCK_MAGIC_LO)
-                || (footer.getLong(16) != APK_SIG_BLOCK_MAGIC_HI)) {
-            throw new SignatureNotFoundException(
-                    "No APK Signing Block before ZIP Central Directory");
-        }
-        // Read and compare size fields
-        long apkSigBlockSizeInFooter = footer.getLong(0);
-        if ((apkSigBlockSizeInFooter < footer.capacity())
-                || (apkSigBlockSizeInFooter > Integer.MAX_VALUE - 8)) {
-            throw new SignatureNotFoundException(
-                    "APK Signing Block size out of range: " + apkSigBlockSizeInFooter);
-        }
-        int totalSize = (int) (apkSigBlockSizeInFooter + 8);
-        long apkSigBlockOffset = centralDirOffset - totalSize;
-        if (apkSigBlockOffset < 0) {
-            throw new SignatureNotFoundException(
-                    "APK Signing Block offset out of range: " + apkSigBlockOffset);
-        }
-        ByteBuffer apkSigBlock = ByteBuffer.allocate(totalSize);
-        apkSigBlock.order(ByteOrder.LITTLE_ENDIAN);
-        apk.seek(apkSigBlockOffset);
-        apk.readFully(apkSigBlock.array(), apkSigBlock.arrayOffset(), apkSigBlock.capacity());
-        long apkSigBlockSizeInHeader = apkSigBlock.getLong(0);
-        if (apkSigBlockSizeInHeader != apkSigBlockSizeInFooter) {
-            throw new SignatureNotFoundException(
-                    "APK Signing Block sizes in header and footer do not match: "
-                            + apkSigBlockSizeInHeader + " vs " + apkSigBlockSizeInFooter);
-        }
-        return Pair.create(apkSigBlock, apkSigBlockOffset);
-    }
-
-    private static ByteBuffer findApkSignatureSchemeV2Block(ByteBuffer apkSigningBlock)
-            throws SignatureNotFoundException {
-        checkByteOrderLittleEndian(apkSigningBlock);
-        // FORMAT:
-        // OFFSET       DATA TYPE  DESCRIPTION
-        // * @+0  bytes uint64:    size in bytes (excluding this field)
-        // * @+8  bytes pairs
-        // * @-24 bytes uint64:    size in bytes (same as the one above)
-        // * @-16 bytes uint128:   magic
-        ByteBuffer pairs = sliceFromTo(apkSigningBlock, 8, apkSigningBlock.capacity() - 24);
-
-        int entryCount = 0;
-        while (pairs.hasRemaining()) {
-            entryCount++;
-            if (pairs.remaining() < 8) {
-                throw new SignatureNotFoundException(
-                        "Insufficient data to read size of APK Signing Block entry #" + entryCount);
-            }
-            long lenLong = pairs.getLong();
-            if ((lenLong < 4) || (lenLong > Integer.MAX_VALUE)) {
-                throw new SignatureNotFoundException(
-                        "APK Signing Block entry #" + entryCount
-                                + " size out of range: " + lenLong);
-            }
-            int len = (int) lenLong;
-            int nextEntryPos = pairs.position() + len;
-            if (len > pairs.remaining()) {
-                throw new SignatureNotFoundException(
-                        "APK Signing Block entry #" + entryCount + " size out of range: " + len
-                                + ", available: " + pairs.remaining());
-            }
-            int id = pairs.getInt();
-            if (id == APK_SIGNATURE_SCHEME_V2_BLOCK_ID) {
-                return getByteBuffer(pairs, len - 4);
-            }
-            pairs.position(nextEntryPos);
-        }
-
-        throw new SignatureNotFoundException(
-                "No APK Signature Scheme v2 block in APK Signing Block");
-    }
-
-    private static void checkByteOrderLittleEndian(ByteBuffer buffer) {
-        if (buffer.order() != ByteOrder.LITTLE_ENDIAN) {
-            throw new IllegalArgumentException("ByteBuffer byte order must be little endian");
-        }
-    }
-
-    /**
-     * {@link DataDigester} that updates multiple {@link MessageDigest}s whenever data is feeded.
-     */
-    private static class MultipleDigestDataDigester implements DataDigester {
-        private final MessageDigest[] mMds;
-
-        MultipleDigestDataDigester(MessageDigest[] mds) {
-            mMds = mds;
-        }
-
-        @Override
-        public void consume(ByteBuffer buffer) {
-            buffer = buffer.slice();
-            for (MessageDigest md : mMds) {
-                buffer.position(0);
-                md.update(buffer);
-            }
-        }
-
-        @Override
-        public void finish() {}
-    }
-
-    /**
-     * For legacy reasons we need to return exactly the original encoded certificate bytes, instead
-     * of letting the underlying implementation have a shot at re-encoding the data.
-     */
-    private static class VerbatimX509Certificate extends WrappedX509Certificate {
-        private byte[] encodedVerbatim;
-
-        public VerbatimX509Certificate(X509Certificate wrapped, byte[] encodedVerbatim) {
-            super(wrapped);
-            this.encodedVerbatim = encodedVerbatim;
-        }
-
-        @Override
-        public byte[] getEncoded() throws CertificateEncodingException {
-            return encodedVerbatim;
-        }
-    }
-
-    private static class WrappedX509Certificate extends X509Certificate {
-        private final X509Certificate wrapped;
-
-        public WrappedX509Certificate(X509Certificate wrapped) {
-            this.wrapped = wrapped;
-        }
-
-        @Override
-        public Set<String> getCriticalExtensionOIDs() {
-            return wrapped.getCriticalExtensionOIDs();
-        }
-
-        @Override
-        public byte[] getExtensionValue(String oid) {
-            return wrapped.getExtensionValue(oid);
-        }
-
-        @Override
-        public Set<String> getNonCriticalExtensionOIDs() {
-            return wrapped.getNonCriticalExtensionOIDs();
-        }
-
-        @Override
-        public boolean hasUnsupportedCriticalExtension() {
-            return wrapped.hasUnsupportedCriticalExtension();
-        }
-
-        @Override
-        public void checkValidity()
-                throws CertificateExpiredException, CertificateNotYetValidException {
-            wrapped.checkValidity();
-        }
-
-        @Override
-        public void checkValidity(Date date)
-                throws CertificateExpiredException, CertificateNotYetValidException {
-            wrapped.checkValidity(date);
-        }
-
-        @Override
-        public int getVersion() {
-            return wrapped.getVersion();
-        }
-
-        @Override
-        public BigInteger getSerialNumber() {
-            return wrapped.getSerialNumber();
-        }
-
-        @Override
-        public Principal getIssuerDN() {
-            return wrapped.getIssuerDN();
-        }
-
-        @Override
-        public Principal getSubjectDN() {
-            return wrapped.getSubjectDN();
-        }
-
-        @Override
-        public Date getNotBefore() {
-            return wrapped.getNotBefore();
-        }
-
-        @Override
-        public Date getNotAfter() {
-            return wrapped.getNotAfter();
-        }
-
-        @Override
-        public byte[] getTBSCertificate() throws CertificateEncodingException {
-            return wrapped.getTBSCertificate();
-        }
-
-        @Override
-        public byte[] getSignature() {
-            return wrapped.getSignature();
-        }
-
-        @Override
-        public String getSigAlgName() {
-            return wrapped.getSigAlgName();
-        }
-
-        @Override
-        public String getSigAlgOID() {
-            return wrapped.getSigAlgOID();
-        }
-
-        @Override
-        public byte[] getSigAlgParams() {
-            return wrapped.getSigAlgParams();
-        }
-
-        @Override
-        public boolean[] getIssuerUniqueID() {
-            return wrapped.getIssuerUniqueID();
-        }
-
-        @Override
-        public boolean[] getSubjectUniqueID() {
-            return wrapped.getSubjectUniqueID();
-        }
-
-        @Override
-        public boolean[] getKeyUsage() {
-            return wrapped.getKeyUsage();
-        }
-
-        @Override
-        public int getBasicConstraints() {
-            return wrapped.getBasicConstraints();
-        }
-
-        @Override
-        public byte[] getEncoded() throws CertificateEncodingException {
-            return wrapped.getEncoded();
-        }
-
-        @Override
-        public void verify(PublicKey key) throws CertificateException, NoSuchAlgorithmException,
-                InvalidKeyException, NoSuchProviderException, SignatureException {
-            wrapped.verify(key);
-        }
-
-        @Override
-        public void verify(PublicKey key, String sigProvider)
-                throws CertificateException, NoSuchAlgorithmException, InvalidKeyException,
-                NoSuchProviderException, SignatureException {
-            wrapped.verify(key, sigProvider);
-        }
-
-        @Override
-        public String toString() {
-            return wrapped.toString();
-        }
-
-        @Override
-        public PublicKey getPublicKey() {
-            return wrapped.getPublicKey();
-        }
-    }
 }
diff --git a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java
new file mode 100644
index 0000000..3271526
--- /dev/null
+++ b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java
@@ -0,0 +1,543 @@
+/*
+ * Copyright (C) 2018 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.util.apk;
+
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_DSA_WITH_SHA256;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_ECDSA_WITH_SHA256;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_ECDSA_WITH_SHA512;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PSS_WITH_SHA256;
+import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PSS_WITH_SHA512;
+import static android.util.apk.ApkSigningBlockUtils.compareSignatureAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.getContentDigestAlgorithmJcaDigestAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.getLengthPrefixedSlice;
+import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmContentDigestAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaKeyAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm;
+import static android.util.apk.ApkSigningBlockUtils.readLengthPrefixedByteArray;
+
+import android.os.Build;
+import android.util.ArrayMap;
+import android.util.Pair;
+
+import java.io.ByteArrayInputStream;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * APK Signature Scheme v3 verifier.
+ *
+ * @hide for internal use only.
+ */
+public class ApkSignatureSchemeV3Verifier {
+
+    /**
+     * ID of this signature scheme as used in X-Android-APK-Signed header used in JAR signing.
+     */
+    public static final int SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID = 3;
+
+    private static final int APK_SIGNATURE_SCHEME_V3_BLOCK_ID = 0xf05368c0;
+
+    /**
+     * Returns {@code true} if the provided APK contains an APK Signature Scheme V3 signature.
+     *
+     * <p><b>NOTE: This method does not verify the signature.</b>
+     */
+    public static boolean hasSignature(String apkFile) throws IOException {
+        try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) {
+            findSignature(apk);
+            return true;
+        } catch (SignatureNotFoundException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Verifies APK Signature Scheme v3 signatures of the provided APK and returns the certificates
+     * associated with each signer.
+     *
+     * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
+     * @throws SecurityException if the APK Signature Scheme v3 signature of this APK does not
+     * verify.
+     * @throws IOException if an I/O error occurs while reading the APK file.
+     */
+    public static VerifiedSigner verify(String apkFile)
+            throws SignatureNotFoundException, SecurityException, IOException {
+        return verify(apkFile, true);
+    }
+
+    /**
+     * Returns the certificates associated with each signer for the given APK without verification.
+     * This method is dangerous and should not be used, unless the caller is absolutely certain the
+     * APK is trusted.  Specifically, verification is only done for the APK Signature Scheme v3
+     * Block while gathering signer information.  The APK contents are not verified.
+     *
+     * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
+     * @throws IOException if an I/O error occurs while reading the APK file.
+     */
+    public static VerifiedSigner plsCertsNoVerifyOnlyCerts(String apkFile)
+            throws SignatureNotFoundException, SecurityException, IOException {
+        return verify(apkFile, false);
+    }
+
+    private static VerifiedSigner verify(String apkFile, boolean verifyIntegrity)
+            throws SignatureNotFoundException, SecurityException, IOException {
+        try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) {
+            return verify(apk, verifyIntegrity);
+        }
+    }
+
+    /**
+     * Verifies APK Signature Scheme v3 signatures of the provided APK and returns the certificates
+     * associated with each signer.
+     *
+     * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
+     * @throws SecurityException if an APK Signature Scheme v3 signature of this APK does not
+     *         verify.
+     * @throws IOException if an I/O error occurs while reading the APK file.
+     */
+    private static VerifiedSigner verify(RandomAccessFile apk, boolean verifyIntegrity)
+            throws SignatureNotFoundException, SecurityException, IOException {
+        SignatureInfo signatureInfo = findSignature(apk);
+        return verify(apk.getFD(), signatureInfo, verifyIntegrity);
+    }
+
+    /**
+     * Returns the APK Signature Scheme v3 block contained in the provided APK file and the
+     * additional information relevant for verifying the block against the file.
+     *
+     * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
+     * @throws IOException if an I/O error occurs while reading the APK file.
+     */
+    private static SignatureInfo findSignature(RandomAccessFile apk)
+            throws IOException, SignatureNotFoundException {
+        return ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V3_BLOCK_ID);
+    }
+
+    /**
+     * Verifies the contents of the provided APK file against the provided APK Signature Scheme v3
+     * Block.
+     *
+     * @param signatureInfo APK Signature Scheme v3 Block and information relevant for verifying it
+     *        against the APK file.
+     */
+    private static VerifiedSigner verify(
+            FileDescriptor apkFileDescriptor,
+            SignatureInfo signatureInfo,
+            boolean doVerifyIntegrity) throws SecurityException {
+        int signerCount = 0;
+        Map<Integer, byte[]> contentDigests = new ArrayMap<>();
+        VerifiedSigner result = null;
+        CertificateFactory certFactory;
+        try {
+            certFactory = CertificateFactory.getInstance("X.509");
+        } catch (CertificateException e) {
+            throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
+        }
+        ByteBuffer signers;
+        try {
+            signers = getLengthPrefixedSlice(signatureInfo.signatureBlock);
+        } catch (IOException e) {
+            throw new SecurityException("Failed to read list of signers", e);
+        }
+        while (signers.hasRemaining()) {
+            try {
+                ByteBuffer signer = getLengthPrefixedSlice(signers);
+                result = verifySigner(signer, contentDigests, certFactory);
+                signerCount++;
+            } catch (PlatformNotSupportedException e) {
+                // this signer is for a different platform, ignore it.
+                continue;
+            } catch (IOException | BufferUnderflowException | SecurityException e) {
+                throw new SecurityException(
+                        "Failed to parse/verify signer #" + signerCount + " block",
+                        e);
+            }
+        }
+
+        if (signerCount < 1 || result == null) {
+            throw new SecurityException("No signers found");
+        }
+
+        if (signerCount != 1) {
+            throw new SecurityException("APK Signature Scheme V3 only supports one signer: "
+                    + "multiple signers found.");
+        }
+
+        if (contentDigests.isEmpty()) {
+            throw new SecurityException("No content digests found");
+        }
+
+        if (doVerifyIntegrity) {
+            ApkSigningBlockUtils.verifyIntegrity(
+                    contentDigests,
+                    apkFileDescriptor,
+                    signatureInfo.apkSigningBlockOffset,
+                    signatureInfo.centralDirOffset,
+                    signatureInfo.eocdOffset,
+                    signatureInfo.eocd);
+        }
+
+        return result;
+    }
+
+    private static VerifiedSigner verifySigner(
+            ByteBuffer signerBlock,
+            Map<Integer, byte[]> contentDigests,
+            CertificateFactory certFactory)
+            throws SecurityException, IOException, PlatformNotSupportedException {
+        ByteBuffer signedData = getLengthPrefixedSlice(signerBlock);
+        int minSdkVersion = signerBlock.getInt();
+        int maxSdkVersion = signerBlock.getInt();
+
+        if (Build.VERSION.SDK_INT < minSdkVersion || Build.VERSION.SDK_INT > maxSdkVersion) {
+            // this signature isn't meant to be used with this platform, skip it.
+            throw new PlatformNotSupportedException(
+                    "Signer not supported by this platform "
+                    + "version. This platform: " + Build.VERSION.SDK_INT
+                    + ", signer minSdkVersion: " + minSdkVersion
+                    + ", maxSdkVersion: " + maxSdkVersion);
+        }
+
+        ByteBuffer signatures = getLengthPrefixedSlice(signerBlock);
+        byte[] publicKeyBytes = readLengthPrefixedByteArray(signerBlock);
+
+        int signatureCount = 0;
+        int bestSigAlgorithm = -1;
+        byte[] bestSigAlgorithmSignatureBytes = null;
+        List<Integer> signaturesSigAlgorithms = new ArrayList<>();
+        while (signatures.hasRemaining()) {
+            signatureCount++;
+            try {
+                ByteBuffer signature = getLengthPrefixedSlice(signatures);
+                if (signature.remaining() < 8) {
+                    throw new SecurityException("Signature record too short");
+                }
+                int sigAlgorithm = signature.getInt();
+                signaturesSigAlgorithms.add(sigAlgorithm);
+                if (!isSupportedSignatureAlgorithm(sigAlgorithm)) {
+                    continue;
+                }
+                if ((bestSigAlgorithm == -1)
+                        || (compareSignatureAlgorithm(sigAlgorithm, bestSigAlgorithm) > 0)) {
+                    bestSigAlgorithm = sigAlgorithm;
+                    bestSigAlgorithmSignatureBytes = readLengthPrefixedByteArray(signature);
+                }
+            } catch (IOException | BufferUnderflowException e) {
+                throw new SecurityException(
+                        "Failed to parse signature record #" + signatureCount,
+                        e);
+            }
+        }
+        if (bestSigAlgorithm == -1) {
+            if (signatureCount == 0) {
+                throw new SecurityException("No signatures found");
+            } else {
+                throw new SecurityException("No supported signatures found");
+            }
+        }
+
+        String keyAlgorithm = getSignatureAlgorithmJcaKeyAlgorithm(bestSigAlgorithm);
+        Pair<String, ? extends AlgorithmParameterSpec> signatureAlgorithmParams =
+                getSignatureAlgorithmJcaSignatureAlgorithm(bestSigAlgorithm);
+        String jcaSignatureAlgorithm = signatureAlgorithmParams.first;
+        AlgorithmParameterSpec jcaSignatureAlgorithmParams = signatureAlgorithmParams.second;
+        boolean sigVerified;
+        try {
+            PublicKey publicKey =
+                    KeyFactory.getInstance(keyAlgorithm)
+                            .generatePublic(new X509EncodedKeySpec(publicKeyBytes));
+            Signature sig = Signature.getInstance(jcaSignatureAlgorithm);
+            sig.initVerify(publicKey);
+            if (jcaSignatureAlgorithmParams != null) {
+                sig.setParameter(jcaSignatureAlgorithmParams);
+            }
+            sig.update(signedData);
+            sigVerified = sig.verify(bestSigAlgorithmSignatureBytes);
+        } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException
+                | InvalidAlgorithmParameterException | SignatureException e) {
+            throw new SecurityException(
+                    "Failed to verify " + jcaSignatureAlgorithm + " signature", e);
+        }
+        if (!sigVerified) {
+            throw new SecurityException(jcaSignatureAlgorithm + " signature did not verify");
+        }
+
+        // Signature over signedData has verified.
+
+        byte[] contentDigest = null;
+        signedData.clear();
+        ByteBuffer digests = getLengthPrefixedSlice(signedData);
+        List<Integer> digestsSigAlgorithms = new ArrayList<>();
+        int digestCount = 0;
+        while (digests.hasRemaining()) {
+            digestCount++;
+            try {
+                ByteBuffer digest = getLengthPrefixedSlice(digests);
+                if (digest.remaining() < 8) {
+                    throw new IOException("Record too short");
+                }
+                int sigAlgorithm = digest.getInt();
+                digestsSigAlgorithms.add(sigAlgorithm);
+                if (sigAlgorithm == bestSigAlgorithm) {
+                    contentDigest = readLengthPrefixedByteArray(digest);
+                }
+            } catch (IOException | BufferUnderflowException e) {
+                throw new IOException("Failed to parse digest record #" + digestCount, e);
+            }
+        }
+
+        if (!signaturesSigAlgorithms.equals(digestsSigAlgorithms)) {
+            throw new SecurityException(
+                    "Signature algorithms don't match between digests and signatures records");
+        }
+        int digestAlgorithm = getSignatureAlgorithmContentDigestAlgorithm(bestSigAlgorithm);
+        byte[] previousSignerDigest = contentDigests.put(digestAlgorithm, contentDigest);
+        if ((previousSignerDigest != null)
+                && (!MessageDigest.isEqual(previousSignerDigest, contentDigest))) {
+            throw new SecurityException(
+                    getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm)
+                    + " contents digest does not match the digest specified by a preceding signer");
+        }
+
+        ByteBuffer certificates = getLengthPrefixedSlice(signedData);
+        List<X509Certificate> certs = new ArrayList<>();
+        int certificateCount = 0;
+        while (certificates.hasRemaining()) {
+            certificateCount++;
+            byte[] encodedCert = readLengthPrefixedByteArray(certificates);
+            X509Certificate certificate;
+            try {
+                certificate = (X509Certificate)
+                        certFactory.generateCertificate(new ByteArrayInputStream(encodedCert));
+            } catch (CertificateException e) {
+                throw new SecurityException("Failed to decode certificate #" + certificateCount, e);
+            }
+            certificate = new VerbatimX509Certificate(
+                    certificate, encodedCert);
+            certs.add(certificate);
+        }
+
+        if (certs.isEmpty()) {
+            throw new SecurityException("No certificates listed");
+        }
+        X509Certificate mainCertificate = certs.get(0);
+        byte[] certificatePublicKeyBytes = mainCertificate.getPublicKey().getEncoded();
+        if (!Arrays.equals(publicKeyBytes, certificatePublicKeyBytes)) {
+            throw new SecurityException(
+                    "Public key mismatch between certificate and signature record");
+        }
+
+        int signedMinSDK = signedData.getInt();
+        if (signedMinSDK != minSdkVersion) {
+            throw new SecurityException(
+                    "minSdkVersion mismatch between signed and unsigned in v3 signer block.");
+        }
+
+        int signedMaxSDK = signedData.getInt();
+        if (signedMaxSDK != maxSdkVersion) {
+            throw new SecurityException(
+                    "maxSdkVersion mismatch between signed and unsigned in v3 signer block.");
+        }
+
+        ByteBuffer additionalAttrs = getLengthPrefixedSlice(signedData);
+        return verifyAdditionalAttributes(additionalAttrs, certs, certFactory);
+    }
+
+    private static final int PROOF_OF_ROTATION_ATTR_ID = 0x3ba06f8c;
+
+    private static VerifiedSigner verifyAdditionalAttributes(ByteBuffer attrs,
+            List<X509Certificate> certs, CertificateFactory certFactory) throws IOException {
+        X509Certificate[] certChain = certs.toArray(new X509Certificate[certs.size()]);
+        VerifiedProofOfRotation por = null;
+
+        while (attrs.hasRemaining()) {
+            ByteBuffer attr = getLengthPrefixedSlice(attrs);
+            if (attr.remaining() < 4) {
+                throw new IOException("Remaining buffer too short to contain additional attribute "
+                        + "ID. Remaining: " + attr.remaining());
+            }
+            int id = attr.getInt();
+            switch(id) {
+                case PROOF_OF_ROTATION_ATTR_ID:
+                    if (por != null) {
+                        throw new SecurityException("Encountered multiple Proof-of-rotation records"
+                                + " when verifying APK Signature Scheme v3 signature.");
+                    }
+                    por = verifyProofOfRotationStruct(attr, certFactory);
+                    break;
+                default:
+                    // not the droid we're looking for, move along, move along.
+                    break;
+            }
+        }
+        return new VerifiedSigner(certChain, por);
+    }
+
+    private static VerifiedProofOfRotation verifyProofOfRotationStruct(
+            ByteBuffer porBuf,
+            CertificateFactory certFactory)
+            throws SecurityException, IOException {
+        int levelCount = 0;
+        int lastSigAlgorithm = -1;
+        X509Certificate lastCert = null;
+        List<X509Certificate> certs = new ArrayList<>();
+        List<Integer> flagsList = new ArrayList<>();
+
+        // Proof-of-rotation struct:
+        // is basically a singly linked list of nodes, called levels here, each of which have the
+        // following structure:
+        // * length-prefix for the entire level
+        //     - length-prefixed signed data (if previous level exists)
+        //         * length-prefixed X509 Certificate
+        //         * uint32 signature algorithm ID describing how this signed data was signed
+        //     - uint32 flags describing how to treat the cert contained in this level
+        //     - uint32 signature algorithm ID to use to verify the signature of the next level. The
+        //         algorithm here must match the one in the signed data section of the next level.
+        //     - length-prefixed signature over the signed data in this level.  The signature here
+        //         is verified using the certificate from the previous level.
+        // The linking is provided by the certificate of each level signing the one of the next.
+        while (porBuf.hasRemaining()) {
+            levelCount++;
+            try {
+                ByteBuffer level = getLengthPrefixedSlice(porBuf);
+                ByteBuffer signedData = getLengthPrefixedSlice(level);
+                int flags = level.getInt();
+                int sigAlgorithm = level.getInt();
+                byte[] signature = readLengthPrefixedByteArray(level);
+
+                if (lastCert != null) {
+                    // Use previous level cert to verify current level
+                    Pair<String, ? extends AlgorithmParameterSpec> sigAlgParams =
+                            getSignatureAlgorithmJcaSignatureAlgorithm(lastSigAlgorithm);
+                    PublicKey publicKey = lastCert.getPublicKey();
+                    Signature sig = Signature.getInstance(sigAlgParams.first);
+                    sig.initVerify(publicKey);
+                    if (sigAlgParams.second != null) {
+                        sig.setParameter(sigAlgParams.second);
+                    }
+                    sig.update(signedData);
+                    if (!sig.verify(signature)) {
+                        throw new SecurityException("Unable to verify signature of certificate #"
+                                + levelCount + " using " + sigAlgParams.first + " when verifying"
+                                + " Proof-of-rotation record");
+                    }
+                }
+
+                byte[] encodedCert = readLengthPrefixedByteArray(signedData);
+                int signedSigAlgorithm = signedData.getInt();
+                if (lastCert != null && lastSigAlgorithm != signedSigAlgorithm) {
+                    throw new SecurityException("Signing algorithm ID mismatch for certificate #"
+                            + levelCount + " when verifying Proof-of-rotation record");
+                }
+                lastCert = (X509Certificate)
+                        certFactory.generateCertificate(new ByteArrayInputStream(encodedCert));
+                lastCert = new VerbatimX509Certificate(lastCert, encodedCert);
+
+                lastSigAlgorithm = sigAlgorithm;
+                certs.add(lastCert);
+                flagsList.add(flags);
+            } catch (IOException | BufferUnderflowException e) {
+                throw new IOException("Failed to parse Proof-of-rotation record", e);
+            } catch (NoSuchAlgorithmException | InvalidKeyException
+                    | InvalidAlgorithmParameterException | SignatureException e) {
+                throw new SecurityException(
+                        "Failed to verify signature over signed data for certificate #"
+                                + levelCount + " when verifying Proof-of-rotation record", e);
+            } catch (CertificateException e) {
+                throw new SecurityException("Failed to decode certificate #" + levelCount
+                        + " when verifying Proof-of-rotation record", e);
+            }
+        }
+        return new VerifiedProofOfRotation(certs, flagsList);
+    }
+
+    private static boolean isSupportedSignatureAlgorithm(int sigAlgorithm) {
+        switch (sigAlgorithm) {
+            case SIGNATURE_RSA_PSS_WITH_SHA256:
+            case SIGNATURE_RSA_PSS_WITH_SHA512:
+            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
+            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
+            case SIGNATURE_ECDSA_WITH_SHA256:
+            case SIGNATURE_ECDSA_WITH_SHA512:
+            case SIGNATURE_DSA_WITH_SHA256:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * Verified processed proof of rotation.
+     *
+     * @hide for internal use only.
+     */
+    public static class VerifiedProofOfRotation {
+        public final List<X509Certificate> certs;
+        public final List<Integer> flagsList;
+
+        public VerifiedProofOfRotation(List<X509Certificate> certs, List<Integer> flagsList) {
+            this.certs = certs;
+            this.flagsList = flagsList;
+        }
+    }
+
+    /**
+     * Verified APK Signature Scheme v3 signer, including the proof of rotation structure.
+     *
+     * @hide for internal use only.
+     */
+    public static class VerifiedSigner {
+        public final X509Certificate[] certs;
+        public final VerifiedProofOfRotation por;
+
+        public VerifiedSigner(X509Certificate[] certs, VerifiedProofOfRotation por) {
+            this.certs = certs;
+            this.por = por;
+        }
+
+    }
+
+    private static class PlatformNotSupportedException extends Exception {
+
+        PlatformNotSupportedException(String s) {
+            super(s);
+        }
+    }
+}
diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java
index 17b11a9..8146729 100644
--- a/core/java/android/util/apk/ApkSignatureVerifier.java
+++ b/core/java/android/util/apk/ApkSignatureVerifier.java
@@ -54,6 +54,7 @@
 
     public static final int VERSION_JAR_SIGNATURE_SCHEME = 1;
     public static final int VERSION_APK_SIGNATURE_SCHEME_V2 = 2;
+    public static final int VERSION_APK_SIGNATURE_SCHEME_V3 = 3;
 
     private static final AtomicReference<byte[]> sBuffer = new AtomicReference<>();
 
@@ -65,7 +66,45 @@
     public static Result verify(String apkPath, int minSignatureSchemeVersion)
             throws PackageParserException {
 
-        // first try v2
+        if (minSignatureSchemeVersion > VERSION_APK_SIGNATURE_SCHEME_V3) {
+            // V3 and before are older than the requested minimum signing version
+            throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+                    "No signature found in package of version " + minSignatureSchemeVersion
+            + " or newer for package " + apkPath);
+        }
+
+        // first try v3
+        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV3");
+        try {
+            ApkSignatureSchemeV3Verifier.VerifiedSigner vSigner =
+                    ApkSignatureSchemeV3Verifier.verify(apkPath);
+            Certificate[][] signerCerts = new Certificate[][] { vSigner.certs };
+            Signature[] signerSigs = convertToSignatures(signerCerts);
+            return new Result(signerCerts, signerSigs, VERSION_APK_SIGNATURE_SCHEME_V3);
+        } catch (SignatureNotFoundException e) {
+            // not signed with v2, try older if allowed
+            if (minSignatureSchemeVersion >= VERSION_APK_SIGNATURE_SCHEME_V3) {
+                throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+                        "No APK Signature Scheme v3 signature in package " + apkPath, e);
+            }
+        } catch (Exception e) {
+            // APK Signature Scheme v2 signature found but did not verify
+            throw new  PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+                    "Failed to collect certificates from " + apkPath
+                            + " using APK Signature Scheme v2", e);
+        } finally {
+            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+        }
+
+        // redundant, protective version check
+        if (minSignatureSchemeVersion > VERSION_APK_SIGNATURE_SCHEME_V2) {
+            // V2 and before are older than the requested minimum signing version
+            throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+                    "No signature found in package of version " + minSignatureSchemeVersion
+                            + " or newer for package " + apkPath);
+        }
+
+        // try v2
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV2");
         try {
             Certificate[][] signerCerts = ApkSignatureSchemeV2Verifier.verify(apkPath);
@@ -87,6 +126,14 @@
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
 
+        // redundant, protective version check
+        if (minSignatureSchemeVersion > VERSION_JAR_SIGNATURE_SCHEME) {
+            // V1 and is older than the requested minimum signing version
+            throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+                    "No signature found in package of version " + minSignatureSchemeVersion
+                            + " or newer for package " + apkPath);
+        }
+
         // v2 didn't work, try jarsigner
         return verifyV1Signature(apkPath, true);
     }
@@ -245,6 +292,44 @@
     public static Result plsCertsNoVerifyOnlyCerts(String apkPath, int minSignatureSchemeVersion)
             throws PackageParserException {
 
+        if (minSignatureSchemeVersion > VERSION_APK_SIGNATURE_SCHEME_V3) {
+            // V3 and before are older than the requested minimum signing version
+            throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+                    "No signature found in package of version " + minSignatureSchemeVersion
+                            + " or newer for package " + apkPath);
+        }
+
+        // first try v3
+        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV3");
+        try {
+            ApkSignatureSchemeV3Verifier.VerifiedSigner vSigner =
+                    ApkSignatureSchemeV3Verifier.plsCertsNoVerifyOnlyCerts(apkPath);
+            Certificate[][] signerCerts = new Certificate[][] { vSigner.certs };
+            Signature[] signerSigs = convertToSignatures(signerCerts);
+            return new Result(signerCerts, signerSigs, VERSION_APK_SIGNATURE_SCHEME_V3);
+        } catch (SignatureNotFoundException e) {
+            // not signed with v2, try older if allowed
+            if (minSignatureSchemeVersion >= VERSION_APK_SIGNATURE_SCHEME_V3) {
+                throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+                        "No APK Signature Scheme v3 signature in package " + apkPath, e);
+            }
+        } catch (Exception e) {
+            // APK Signature Scheme v2 signature found but did not verify
+            throw new  PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+                    "Failed to collect certificates from " + apkPath
+                            + " using APK Signature Scheme v2", e);
+        } finally {
+            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+        }
+
+        // redundant, protective version check
+        if (minSignatureSchemeVersion > VERSION_APK_SIGNATURE_SCHEME_V2) {
+            // V2 and before are older than the requested minimum signing version
+            throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+                    "No signature found in package of version " + minSignatureSchemeVersion
+                            + " or newer for package " + apkPath);
+        }
+
         // first try v2
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "certsOnlyV2");
         try {
@@ -267,6 +352,14 @@
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
 
+        // redundant, protective version check
+        if (minSignatureSchemeVersion > VERSION_JAR_SIGNATURE_SCHEME) {
+            // V1 and is older than the requested minimum signing version
+            throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+                    "No signature found in package of version " + minSignatureSchemeVersion
+                            + " or newer for package " + apkPath);
+        }
+
         // v2 didn't work, try jarsigner
         return verifyV1Signature(apkPath, false);
     }
diff --git a/core/java/android/util/apk/ApkSigningBlockUtils.java b/core/java/android/util/apk/ApkSigningBlockUtils.java
new file mode 100644
index 0000000..9279510
--- /dev/null
+++ b/core/java/android/util/apk/ApkSigningBlockUtils.java
@@ -0,0 +1,663 @@
+/*
+ * Copyright (C) 2018 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.util.apk;
+
+import android.util.Pair;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.security.DigestException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.MGF1ParameterSpec;
+import java.security.spec.PSSParameterSpec;
+import java.util.Map;
+
+/**
+ * Utility class for an APK Signature Scheme using the APK Signing Block.
+ *
+ * @hide for internal use only.
+ */
+final class ApkSigningBlockUtils {
+
+    private ApkSigningBlockUtils() {
+    }
+
+    /**
+     * Returns the APK Signature Scheme block contained in the provided APK file and the
+     * additional information relevant for verifying the block against the file.
+     *
+     * @param blockId the ID value in the APK Signing Block's sequence of ID-value pairs
+     *                identifying the appropriate block to find, e.g. the APK Signature Scheme v2
+     *                block ID.
+     *
+     * @throws SignatureNotFoundException if the APK is not signed using this scheme.
+     * @throws IOException if an I/O error occurs while reading the APK file.
+     */
+    static SignatureInfo findSignature(RandomAccessFile apk, int blockId)
+            throws IOException, SignatureNotFoundException {
+        // Find the ZIP End of Central Directory (EoCD) record.
+        Pair<ByteBuffer, Long> eocdAndOffsetInFile = getEocd(apk);
+        ByteBuffer eocd = eocdAndOffsetInFile.first;
+        long eocdOffset = eocdAndOffsetInFile.second;
+        if (ZipUtils.isZip64EndOfCentralDirectoryLocatorPresent(apk, eocdOffset)) {
+            throw new SignatureNotFoundException("ZIP64 APK not supported");
+        }
+
+        // Find the APK Signing Block. The block immediately precedes the Central Directory.
+        long centralDirOffset = getCentralDirOffset(eocd, eocdOffset);
+        Pair<ByteBuffer, Long> apkSigningBlockAndOffsetInFile =
+                findApkSigningBlock(apk, centralDirOffset);
+        ByteBuffer apkSigningBlock = apkSigningBlockAndOffsetInFile.first;
+        long apkSigningBlockOffset = apkSigningBlockAndOffsetInFile.second;
+
+        // Find the APK Signature Scheme Block inside the APK Signing Block.
+        ByteBuffer apkSignatureSchemeBlock = findApkSignatureSchemeBlock(apkSigningBlock,
+                blockId);
+
+        return new SignatureInfo(
+                apkSignatureSchemeBlock,
+                apkSigningBlockOffset,
+                centralDirOffset,
+                eocdOffset,
+                eocd);
+    }
+
+    static void verifyIntegrity(
+            Map<Integer, byte[]> expectedDigests,
+            FileDescriptor apkFileDescriptor,
+            long apkSigningBlockOffset,
+            long centralDirOffset,
+            long eocdOffset,
+            ByteBuffer eocdBuf) throws SecurityException {
+
+        if (expectedDigests.isEmpty()) {
+            throw new SecurityException("No digests provided");
+        }
+
+        // We need to verify the integrity of the following three sections of the file:
+        // 1. Everything up to the start of the APK Signing Block.
+        // 2. ZIP Central Directory.
+        // 3. ZIP End of Central Directory (EoCD).
+        // Each of these sections is represented as a separate DataSource instance below.
+
+        // To handle large APKs, these sections are read in 1 MB chunks using memory-mapped I/O to
+        // avoid wasting physical memory. In most APK verification scenarios, the contents of the
+        // APK are already there in the OS's page cache and thus mmap does not use additional
+        // physical memory.
+        DataSource beforeApkSigningBlock =
+                new MemoryMappedFileDataSource(apkFileDescriptor, 0, apkSigningBlockOffset);
+        DataSource centralDir =
+                new MemoryMappedFileDataSource(
+                        apkFileDescriptor, centralDirOffset, eocdOffset - centralDirOffset);
+
+        // For the purposes of integrity verification, ZIP End of Central Directory's field Start of
+        // Central Directory must be considered to point to the offset of the APK Signing Block.
+        eocdBuf = eocdBuf.duplicate();
+        eocdBuf.order(ByteOrder.LITTLE_ENDIAN);
+        ZipUtils.setZipEocdCentralDirectoryOffset(eocdBuf, apkSigningBlockOffset);
+        DataSource eocd = new ByteBufferDataSource(eocdBuf);
+
+        int[] digestAlgorithms = new int[expectedDigests.size()];
+        int digestAlgorithmCount = 0;
+        for (int digestAlgorithm : expectedDigests.keySet()) {
+            digestAlgorithms[digestAlgorithmCount] = digestAlgorithm;
+            digestAlgorithmCount++;
+        }
+        byte[][] actualDigests;
+        try {
+            actualDigests =
+                    computeContentDigests(
+                            digestAlgorithms,
+                            new DataSource[] {beforeApkSigningBlock, centralDir, eocd});
+        } catch (DigestException e) {
+            throw new SecurityException("Failed to compute digest(s) of contents", e);
+        }
+        for (int i = 0; i < digestAlgorithms.length; i++) {
+            int digestAlgorithm = digestAlgorithms[i];
+            byte[] expectedDigest = expectedDigests.get(digestAlgorithm);
+            byte[] actualDigest = actualDigests[i];
+            if (!MessageDigest.isEqual(expectedDigest, actualDigest)) {
+                throw new SecurityException(
+                        getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm)
+                                + " digest of contents did not verify");
+            }
+        }
+    }
+
+    private static byte[][] computeContentDigests(
+            int[] digestAlgorithms,
+            DataSource[] contents) throws DigestException {
+        // For each digest algorithm the result is computed as follows:
+        // 1. Each segment of contents is split into consecutive chunks of 1 MB in size.
+        //    The final chunk will be shorter iff the length of segment is not a multiple of 1 MB.
+        //    No chunks are produced for empty (zero length) segments.
+        // 2. The digest of each chunk is computed over the concatenation of byte 0xa5, the chunk's
+        //    length in bytes (uint32 little-endian) and the chunk's contents.
+        // 3. The output digest is computed over the concatenation of the byte 0x5a, the number of
+        //    chunks (uint32 little-endian) and the concatenation of digests of chunks of all
+        //    segments in-order.
+
+        long totalChunkCountLong = 0;
+        for (DataSource input : contents) {
+            totalChunkCountLong += getChunkCount(input.size());
+        }
+        if (totalChunkCountLong >= Integer.MAX_VALUE / 1024) {
+            throw new DigestException("Too many chunks: " + totalChunkCountLong);
+        }
+        int totalChunkCount = (int) totalChunkCountLong;
+
+        byte[][] digestsOfChunks = new byte[digestAlgorithms.length][];
+        for (int i = 0; i < digestAlgorithms.length; i++) {
+            int digestAlgorithm = digestAlgorithms[i];
+            int digestOutputSizeBytes = getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm);
+            byte[] concatenationOfChunkCountAndChunkDigests =
+                    new byte[5 + totalChunkCount * digestOutputSizeBytes];
+            concatenationOfChunkCountAndChunkDigests[0] = 0x5a;
+            setUnsignedInt32LittleEndian(
+                    totalChunkCount,
+                    concatenationOfChunkCountAndChunkDigests,
+                    1);
+            digestsOfChunks[i] = concatenationOfChunkCountAndChunkDigests;
+        }
+
+        byte[] chunkContentPrefix = new byte[5];
+        chunkContentPrefix[0] = (byte) 0xa5;
+        int chunkIndex = 0;
+        MessageDigest[] mds = new MessageDigest[digestAlgorithms.length];
+        for (int i = 0; i < digestAlgorithms.length; i++) {
+            String jcaAlgorithmName =
+                    getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithms[i]);
+            try {
+                mds[i] = MessageDigest.getInstance(jcaAlgorithmName);
+            } catch (NoSuchAlgorithmException e) {
+                throw new RuntimeException(jcaAlgorithmName + " digest not supported", e);
+            }
+        }
+        // TODO: Compute digests of chunks in parallel when beneficial. This requires some research
+        // into how to parallelize (if at all) based on the capabilities of the hardware on which
+        // this code is running and based on the size of input.
+        DataDigester digester = new MultipleDigestDataDigester(mds);
+        int dataSourceIndex = 0;
+        for (DataSource input : contents) {
+            long inputOffset = 0;
+            long inputRemaining = input.size();
+            while (inputRemaining > 0) {
+                int chunkSize = (int) Math.min(inputRemaining, CHUNK_SIZE_BYTES);
+                setUnsignedInt32LittleEndian(chunkSize, chunkContentPrefix, 1);
+                for (int i = 0; i < mds.length; i++) {
+                    mds[i].update(chunkContentPrefix);
+                }
+                try {
+                    input.feedIntoDataDigester(digester, inputOffset, chunkSize);
+                } catch (IOException e) {
+                    throw new DigestException(
+                            "Failed to digest chunk #" + chunkIndex + " of section #"
+                                    + dataSourceIndex,
+                            e);
+                }
+                for (int i = 0; i < digestAlgorithms.length; i++) {
+                    int digestAlgorithm = digestAlgorithms[i];
+                    byte[] concatenationOfChunkCountAndChunkDigests = digestsOfChunks[i];
+                    int expectedDigestSizeBytes =
+                            getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm);
+                    MessageDigest md = mds[i];
+                    int actualDigestSizeBytes =
+                            md.digest(
+                                    concatenationOfChunkCountAndChunkDigests,
+                                    5 + chunkIndex * expectedDigestSizeBytes,
+                                    expectedDigestSizeBytes);
+                    if (actualDigestSizeBytes != expectedDigestSizeBytes) {
+                        throw new RuntimeException(
+                                "Unexpected output size of " + md.getAlgorithm() + " digest: "
+                                        + actualDigestSizeBytes);
+                    }
+                }
+                inputOffset += chunkSize;
+                inputRemaining -= chunkSize;
+                chunkIndex++;
+            }
+            dataSourceIndex++;
+        }
+
+        byte[][] result = new byte[digestAlgorithms.length][];
+        for (int i = 0; i < digestAlgorithms.length; i++) {
+            int digestAlgorithm = digestAlgorithms[i];
+            byte[] input = digestsOfChunks[i];
+            String jcaAlgorithmName = getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm);
+            MessageDigest md;
+            try {
+                md = MessageDigest.getInstance(jcaAlgorithmName);
+            } catch (NoSuchAlgorithmException e) {
+                throw new RuntimeException(jcaAlgorithmName + " digest not supported", e);
+            }
+            byte[] output = md.digest(input);
+            result[i] = output;
+        }
+        return result;
+    }
+
+    /**
+     * Returns the ZIP End of Central Directory (EoCD) and its offset in the file.
+     *
+     * @throws IOException if an I/O error occurs while reading the file.
+     * @throws SignatureNotFoundException if the EoCD could not be found.
+     */
+    static Pair<ByteBuffer, Long> getEocd(RandomAccessFile apk)
+            throws IOException, SignatureNotFoundException {
+        Pair<ByteBuffer, Long> eocdAndOffsetInFile =
+                ZipUtils.findZipEndOfCentralDirectoryRecord(apk);
+        if (eocdAndOffsetInFile == null) {
+            throw new SignatureNotFoundException(
+                    "Not an APK file: ZIP End of Central Directory record not found");
+        }
+        return eocdAndOffsetInFile;
+    }
+
+    static long getCentralDirOffset(ByteBuffer eocd, long eocdOffset)
+            throws SignatureNotFoundException {
+        // Look up the offset of ZIP Central Directory.
+        long centralDirOffset = ZipUtils.getZipEocdCentralDirectoryOffset(eocd);
+        if (centralDirOffset > eocdOffset) {
+            throw new SignatureNotFoundException(
+                    "ZIP Central Directory offset out of range: " + centralDirOffset
+                    + ". ZIP End of Central Directory offset: " + eocdOffset);
+        }
+        long centralDirSize = ZipUtils.getZipEocdCentralDirectorySizeBytes(eocd);
+        if (centralDirOffset + centralDirSize != eocdOffset) {
+            throw new SignatureNotFoundException(
+                    "ZIP Central Directory is not immediately followed by End of Central"
+                    + " Directory");
+        }
+        return centralDirOffset;
+    }
+
+    private static long getChunkCount(long inputSizeBytes) {
+        return (inputSizeBytes + CHUNK_SIZE_BYTES - 1) / CHUNK_SIZE_BYTES;
+    }
+
+    private static final int CHUNK_SIZE_BYTES = 1024 * 1024;
+
+    static final int SIGNATURE_RSA_PSS_WITH_SHA256 = 0x0101;
+    static final int SIGNATURE_RSA_PSS_WITH_SHA512 = 0x0102;
+    static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256 = 0x0103;
+    static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512 = 0x0104;
+    static final int SIGNATURE_ECDSA_WITH_SHA256 = 0x0201;
+    static final int SIGNATURE_ECDSA_WITH_SHA512 = 0x0202;
+    static final int SIGNATURE_DSA_WITH_SHA256 = 0x0301;
+
+    static final int CONTENT_DIGEST_CHUNKED_SHA256 = 1;
+    static final int CONTENT_DIGEST_CHUNKED_SHA512 = 2;
+
+    static int compareSignatureAlgorithm(int sigAlgorithm1, int sigAlgorithm2) {
+        int digestAlgorithm1 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm1);
+        int digestAlgorithm2 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm2);
+        return compareContentDigestAlgorithm(digestAlgorithm1, digestAlgorithm2);
+    }
+
+    private static int compareContentDigestAlgorithm(int digestAlgorithm1, int digestAlgorithm2) {
+        switch (digestAlgorithm1) {
+            case CONTENT_DIGEST_CHUNKED_SHA256:
+                switch (digestAlgorithm2) {
+                    case CONTENT_DIGEST_CHUNKED_SHA256:
+                        return 0;
+                    case CONTENT_DIGEST_CHUNKED_SHA512:
+                        return -1;
+                    default:
+                        throw new IllegalArgumentException(
+                                "Unknown digestAlgorithm2: " + digestAlgorithm2);
+                }
+            case CONTENT_DIGEST_CHUNKED_SHA512:
+                switch (digestAlgorithm2) {
+                    case CONTENT_DIGEST_CHUNKED_SHA256:
+                        return 1;
+                    case CONTENT_DIGEST_CHUNKED_SHA512:
+                        return 0;
+                    default:
+                        throw new IllegalArgumentException(
+                                "Unknown digestAlgorithm2: " + digestAlgorithm2);
+                }
+            default:
+                throw new IllegalArgumentException("Unknown digestAlgorithm1: " + digestAlgorithm1);
+        }
+    }
+
+    static int getSignatureAlgorithmContentDigestAlgorithm(int sigAlgorithm) {
+        switch (sigAlgorithm) {
+            case SIGNATURE_RSA_PSS_WITH_SHA256:
+            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
+            case SIGNATURE_ECDSA_WITH_SHA256:
+            case SIGNATURE_DSA_WITH_SHA256:
+                return CONTENT_DIGEST_CHUNKED_SHA256;
+            case SIGNATURE_RSA_PSS_WITH_SHA512:
+            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
+            case SIGNATURE_ECDSA_WITH_SHA512:
+                return CONTENT_DIGEST_CHUNKED_SHA512;
+            default:
+                throw new IllegalArgumentException(
+                        "Unknown signature algorithm: 0x"
+                                + Long.toHexString(sigAlgorithm & 0xffffffff));
+        }
+    }
+
+    static String getContentDigestAlgorithmJcaDigestAlgorithm(int digestAlgorithm) {
+        switch (digestAlgorithm) {
+            case CONTENT_DIGEST_CHUNKED_SHA256:
+                return "SHA-256";
+            case CONTENT_DIGEST_CHUNKED_SHA512:
+                return "SHA-512";
+            default:
+                throw new IllegalArgumentException(
+                        "Unknown content digest algorthm: " + digestAlgorithm);
+        }
+    }
+
+    private static int getContentDigestAlgorithmOutputSizeBytes(int digestAlgorithm) {
+        switch (digestAlgorithm) {
+            case CONTENT_DIGEST_CHUNKED_SHA256:
+                return 256 / 8;
+            case CONTENT_DIGEST_CHUNKED_SHA512:
+                return 512 / 8;
+            default:
+                throw new IllegalArgumentException(
+                        "Unknown content digest algorthm: " + digestAlgorithm);
+        }
+    }
+
+    static String getSignatureAlgorithmJcaKeyAlgorithm(int sigAlgorithm) {
+        switch (sigAlgorithm) {
+            case SIGNATURE_RSA_PSS_WITH_SHA256:
+            case SIGNATURE_RSA_PSS_WITH_SHA512:
+            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
+            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
+                return "RSA";
+            case SIGNATURE_ECDSA_WITH_SHA256:
+            case SIGNATURE_ECDSA_WITH_SHA512:
+                return "EC";
+            case SIGNATURE_DSA_WITH_SHA256:
+                return "DSA";
+            default:
+                throw new IllegalArgumentException(
+                        "Unknown signature algorithm: 0x"
+                                + Long.toHexString(sigAlgorithm & 0xffffffff));
+        }
+    }
+
+    static Pair<String, ? extends AlgorithmParameterSpec>
+            getSignatureAlgorithmJcaSignatureAlgorithm(int sigAlgorithm) {
+        switch (sigAlgorithm) {
+            case SIGNATURE_RSA_PSS_WITH_SHA256:
+                return Pair.create(
+                        "SHA256withRSA/PSS",
+                        new PSSParameterSpec(
+                                "SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 256 / 8, 1));
+            case SIGNATURE_RSA_PSS_WITH_SHA512:
+                return Pair.create(
+                        "SHA512withRSA/PSS",
+                        new PSSParameterSpec(
+                                "SHA-512", "MGF1", MGF1ParameterSpec.SHA512, 512 / 8, 1));
+            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
+                return Pair.create("SHA256withRSA", null);
+            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
+                return Pair.create("SHA512withRSA", null);
+            case SIGNATURE_ECDSA_WITH_SHA256:
+                return Pair.create("SHA256withECDSA", null);
+            case SIGNATURE_ECDSA_WITH_SHA512:
+                return Pair.create("SHA512withECDSA", null);
+            case SIGNATURE_DSA_WITH_SHA256:
+                return Pair.create("SHA256withDSA", null);
+            default:
+                throw new IllegalArgumentException(
+                        "Unknown signature algorithm: 0x"
+                                + Long.toHexString(sigAlgorithm & 0xffffffff));
+        }
+    }
+
+    /**
+     * Returns new byte buffer whose content is a shared subsequence of this buffer's content
+     * between the specified start (inclusive) and end (exclusive) positions. As opposed to
+     * {@link ByteBuffer#slice()}, the returned buffer's byte order is the same as the source
+     * buffer's byte order.
+     */
+    static ByteBuffer sliceFromTo(ByteBuffer source, int start, int end) {
+        if (start < 0) {
+            throw new IllegalArgumentException("start: " + start);
+        }
+        if (end < start) {
+            throw new IllegalArgumentException("end < start: " + end + " < " + start);
+        }
+        int capacity = source.capacity();
+        if (end > source.capacity()) {
+            throw new IllegalArgumentException("end > capacity: " + end + " > " + capacity);
+        }
+        int originalLimit = source.limit();
+        int originalPosition = source.position();
+        try {
+            source.position(0);
+            source.limit(end);
+            source.position(start);
+            ByteBuffer result = source.slice();
+            result.order(source.order());
+            return result;
+        } finally {
+            source.position(0);
+            source.limit(originalLimit);
+            source.position(originalPosition);
+        }
+    }
+
+    /**
+     * Relative <em>get</em> method for reading {@code size} number of bytes from the current
+     * position of this buffer.
+     *
+     * <p>This method reads the next {@code size} bytes at this buffer's current position,
+     * returning them as a {@code ByteBuffer} with start set to 0, limit and capacity set to
+     * {@code size}, byte order set to this buffer's byte order; and then increments the position by
+     * {@code size}.
+     */
+    static ByteBuffer getByteBuffer(ByteBuffer source, int size)
+            throws BufferUnderflowException {
+        if (size < 0) {
+            throw new IllegalArgumentException("size: " + size);
+        }
+        int originalLimit = source.limit();
+        int position = source.position();
+        int limit = position + size;
+        if ((limit < position) || (limit > originalLimit)) {
+            throw new BufferUnderflowException();
+        }
+        source.limit(limit);
+        try {
+            ByteBuffer result = source.slice();
+            result.order(source.order());
+            source.position(limit);
+            return result;
+        } finally {
+            source.limit(originalLimit);
+        }
+    }
+
+    static ByteBuffer getLengthPrefixedSlice(ByteBuffer source) throws IOException {
+        if (source.remaining() < 4) {
+            throw new IOException(
+                    "Remaining buffer too short to contain length of length-prefixed field."
+                            + " Remaining: " + source.remaining());
+        }
+        int len = source.getInt();
+        if (len < 0) {
+            throw new IllegalArgumentException("Negative length");
+        } else if (len > source.remaining()) {
+            throw new IOException("Length-prefixed field longer than remaining buffer."
+                    + " Field length: " + len + ", remaining: " + source.remaining());
+        }
+        return getByteBuffer(source, len);
+    }
+
+    static byte[] readLengthPrefixedByteArray(ByteBuffer buf) throws IOException {
+        int len = buf.getInt();
+        if (len < 0) {
+            throw new IOException("Negative length");
+        } else if (len > buf.remaining()) {
+            throw new IOException("Underflow while reading length-prefixed value. Length: " + len
+                    + ", available: " + buf.remaining());
+        }
+        byte[] result = new byte[len];
+        buf.get(result);
+        return result;
+    }
+
+    static void setUnsignedInt32LittleEndian(int value, byte[] result, int offset) {
+        result[offset] = (byte) (value & 0xff);
+        result[offset + 1] = (byte) ((value >>> 8) & 0xff);
+        result[offset + 2] = (byte) ((value >>> 16) & 0xff);
+        result[offset + 3] = (byte) ((value >>> 24) & 0xff);
+    }
+
+    private static final long APK_SIG_BLOCK_MAGIC_HI = 0x3234206b636f6c42L;
+    private static final long APK_SIG_BLOCK_MAGIC_LO = 0x20676953204b5041L;
+    private static final int APK_SIG_BLOCK_MIN_SIZE = 32;
+
+    static Pair<ByteBuffer, Long> findApkSigningBlock(
+            RandomAccessFile apk, long centralDirOffset)
+                    throws IOException, SignatureNotFoundException {
+        // FORMAT:
+        // OFFSET       DATA TYPE  DESCRIPTION
+        // * @+0  bytes uint64:    size in bytes (excluding this field)
+        // * @+8  bytes payload
+        // * @-24 bytes uint64:    size in bytes (same as the one above)
+        // * @-16 bytes uint128:   magic
+
+        if (centralDirOffset < APK_SIG_BLOCK_MIN_SIZE) {
+            throw new SignatureNotFoundException(
+                    "APK too small for APK Signing Block. ZIP Central Directory offset: "
+                            + centralDirOffset);
+        }
+        // Read the magic and offset in file from the footer section of the block:
+        // * uint64:   size of block
+        // * 16 bytes: magic
+        ByteBuffer footer = ByteBuffer.allocate(24);
+        footer.order(ByteOrder.LITTLE_ENDIAN);
+        apk.seek(centralDirOffset - footer.capacity());
+        apk.readFully(footer.array(), footer.arrayOffset(), footer.capacity());
+        if ((footer.getLong(8) != APK_SIG_BLOCK_MAGIC_LO)
+                || (footer.getLong(16) != APK_SIG_BLOCK_MAGIC_HI)) {
+            throw new SignatureNotFoundException(
+                    "No APK Signing Block before ZIP Central Directory");
+        }
+        // Read and compare size fields
+        long apkSigBlockSizeInFooter = footer.getLong(0);
+        if ((apkSigBlockSizeInFooter < footer.capacity())
+                || (apkSigBlockSizeInFooter > Integer.MAX_VALUE - 8)) {
+            throw new SignatureNotFoundException(
+                    "APK Signing Block size out of range: " + apkSigBlockSizeInFooter);
+        }
+        int totalSize = (int) (apkSigBlockSizeInFooter + 8);
+        long apkSigBlockOffset = centralDirOffset - totalSize;
+        if (apkSigBlockOffset < 0) {
+            throw new SignatureNotFoundException(
+                    "APK Signing Block offset out of range: " + apkSigBlockOffset);
+        }
+        ByteBuffer apkSigBlock = ByteBuffer.allocate(totalSize);
+        apkSigBlock.order(ByteOrder.LITTLE_ENDIAN);
+        apk.seek(apkSigBlockOffset);
+        apk.readFully(apkSigBlock.array(), apkSigBlock.arrayOffset(), apkSigBlock.capacity());
+        long apkSigBlockSizeInHeader = apkSigBlock.getLong(0);
+        if (apkSigBlockSizeInHeader != apkSigBlockSizeInFooter) {
+            throw new SignatureNotFoundException(
+                    "APK Signing Block sizes in header and footer do not match: "
+                            + apkSigBlockSizeInHeader + " vs " + apkSigBlockSizeInFooter);
+        }
+        return Pair.create(apkSigBlock, apkSigBlockOffset);
+    }
+
+    static ByteBuffer findApkSignatureSchemeBlock(ByteBuffer apkSigningBlock, int blockId)
+            throws SignatureNotFoundException {
+        checkByteOrderLittleEndian(apkSigningBlock);
+        // FORMAT:
+        // OFFSET       DATA TYPE  DESCRIPTION
+        // * @+0  bytes uint64:    size in bytes (excluding this field)
+        // * @+8  bytes pairs
+        // * @-24 bytes uint64:    size in bytes (same as the one above)
+        // * @-16 bytes uint128:   magic
+        ByteBuffer pairs = sliceFromTo(apkSigningBlock, 8, apkSigningBlock.capacity() - 24);
+
+        int entryCount = 0;
+        while (pairs.hasRemaining()) {
+            entryCount++;
+            if (pairs.remaining() < 8) {
+                throw new SignatureNotFoundException(
+                        "Insufficient data to read size of APK Signing Block entry #" + entryCount);
+            }
+            long lenLong = pairs.getLong();
+            if ((lenLong < 4) || (lenLong > Integer.MAX_VALUE)) {
+                throw new SignatureNotFoundException(
+                        "APK Signing Block entry #" + entryCount
+                                + " size out of range: " + lenLong);
+            }
+            int len = (int) lenLong;
+            int nextEntryPos = pairs.position() + len;
+            if (len > pairs.remaining()) {
+                throw new SignatureNotFoundException(
+                        "APK Signing Block entry #" + entryCount + " size out of range: " + len
+                                + ", available: " + pairs.remaining());
+            }
+            int id = pairs.getInt();
+            if (id == blockId) {
+                return getByteBuffer(pairs, len - 4);
+            }
+            pairs.position(nextEntryPos);
+        }
+
+        throw new SignatureNotFoundException(
+                "No block with ID " + blockId + " in APK Signing Block.");
+    }
+
+    private static void checkByteOrderLittleEndian(ByteBuffer buffer) {
+        if (buffer.order() != ByteOrder.LITTLE_ENDIAN) {
+            throw new IllegalArgumentException("ByteBuffer byte order must be little endian");
+        }
+    }
+
+    /**
+     * {@link DataDigester} that updates multiple {@link MessageDigest}s whenever data is fed.
+     */
+    private static class MultipleDigestDataDigester implements DataDigester {
+        private final MessageDigest[] mMds;
+
+        MultipleDigestDataDigester(MessageDigest[] mds) {
+            mMds = mds;
+        }
+
+        @Override
+        public void consume(ByteBuffer buffer) {
+            buffer = buffer.slice();
+            for (MessageDigest md : mMds) {
+                buffer.position(0);
+                md.update(buffer);
+            }
+        }
+
+        @Override
+        public void finish() {}
+    }
+
+}
diff --git a/core/java/android/util/apk/VerbatimX509Certificate.java b/core/java/android/util/apk/VerbatimX509Certificate.java
new file mode 100644
index 0000000..9984c6d
--- /dev/null
+++ b/core/java/android/util/apk/VerbatimX509Certificate.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2018 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.util.apk;
+
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+
+/**
+ * For legacy reasons we need to return exactly the original encoded certificate bytes, instead
+ * of letting the underlying implementation have a shot at re-encoding the data.
+ */
+class VerbatimX509Certificate extends WrappedX509Certificate {
+    private final byte[] mEncodedVerbatim;
+
+    VerbatimX509Certificate(X509Certificate wrapped, byte[] encodedVerbatim) {
+        super(wrapped);
+        this.mEncodedVerbatim = encodedVerbatim;
+    }
+
+    @Override
+    public byte[] getEncoded() throws CertificateEncodingException {
+        return mEncodedVerbatim;
+    }
+}
diff --git a/core/java/android/util/apk/WrappedX509Certificate.java b/core/java/android/util/apk/WrappedX509Certificate.java
new file mode 100644
index 0000000..fdaa420
--- /dev/null
+++ b/core/java/android/util/apk/WrappedX509Certificate.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2018 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.util.apk;
+
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Principal;
+import java.security.PublicKey;
+import java.security.SignatureException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+import java.util.Set;
+
+class WrappedX509Certificate extends X509Certificate {
+    private final X509Certificate mWrapped;
+
+    WrappedX509Certificate(X509Certificate wrapped) {
+        this.mWrapped = wrapped;
+    }
+
+    @Override
+    public Set<String> getCriticalExtensionOIDs() {
+        return mWrapped.getCriticalExtensionOIDs();
+    }
+
+    @Override
+    public byte[] getExtensionValue(String oid) {
+        return mWrapped.getExtensionValue(oid);
+    }
+
+    @Override
+    public Set<String> getNonCriticalExtensionOIDs() {
+        return mWrapped.getNonCriticalExtensionOIDs();
+    }
+
+    @Override
+    public boolean hasUnsupportedCriticalExtension() {
+        return mWrapped.hasUnsupportedCriticalExtension();
+    }
+
+    @Override
+    public void checkValidity()
+            throws CertificateExpiredException, CertificateNotYetValidException {
+        mWrapped.checkValidity();
+    }
+
+    @Override
+    public void checkValidity(Date date)
+            throws CertificateExpiredException, CertificateNotYetValidException {
+        mWrapped.checkValidity(date);
+    }
+
+    @Override
+    public int getVersion() {
+        return mWrapped.getVersion();
+    }
+
+    @Override
+    public BigInteger getSerialNumber() {
+        return mWrapped.getSerialNumber();
+    }
+
+    @Override
+    public Principal getIssuerDN() {
+        return mWrapped.getIssuerDN();
+    }
+
+    @Override
+    public Principal getSubjectDN() {
+        return mWrapped.getSubjectDN();
+    }
+
+    @Override
+    public Date getNotBefore() {
+        return mWrapped.getNotBefore();
+    }
+
+    @Override
+    public Date getNotAfter() {
+        return mWrapped.getNotAfter();
+    }
+
+    @Override
+    public byte[] getTBSCertificate() throws CertificateEncodingException {
+        return mWrapped.getTBSCertificate();
+    }
+
+    @Override
+    public byte[] getSignature() {
+        return mWrapped.getSignature();
+    }
+
+    @Override
+    public String getSigAlgName() {
+        return mWrapped.getSigAlgName();
+    }
+
+    @Override
+    public String getSigAlgOID() {
+        return mWrapped.getSigAlgOID();
+    }
+
+    @Override
+    public byte[] getSigAlgParams() {
+        return mWrapped.getSigAlgParams();
+    }
+
+    @Override
+    public boolean[] getIssuerUniqueID() {
+        return mWrapped.getIssuerUniqueID();
+    }
+
+    @Override
+    public boolean[] getSubjectUniqueID() {
+        return mWrapped.getSubjectUniqueID();
+    }
+
+    @Override
+    public boolean[] getKeyUsage() {
+        return mWrapped.getKeyUsage();
+    }
+
+    @Override
+    public int getBasicConstraints() {
+        return mWrapped.getBasicConstraints();
+    }
+
+    @Override
+    public byte[] getEncoded() throws CertificateEncodingException {
+        return mWrapped.getEncoded();
+    }
+
+    @Override
+    public void verify(PublicKey key) throws CertificateException, NoSuchAlgorithmException,
+            InvalidKeyException, NoSuchProviderException, SignatureException {
+        mWrapped.verify(key);
+    }
+
+    @Override
+    public void verify(PublicKey key, String sigProvider)
+            throws CertificateException, NoSuchAlgorithmException, InvalidKeyException,
+            NoSuchProviderException, SignatureException {
+        mWrapped.verify(key, sigProvider);
+    }
+
+    @Override
+    public String toString() {
+        return mWrapped.toString();
+    }
+
+    @Override
+    public PublicKey getPublicKey() {
+        return mWrapped.getPublicKey();
+    }
+}
diff --git a/core/java/android/util/jar/StrictJarVerifier.java b/core/java/android/util/jar/StrictJarVerifier.java
index debc170..4525490 100644
--- a/core/java/android/util/jar/StrictJarVerifier.java
+++ b/core/java/android/util/jar/StrictJarVerifier.java
@@ -18,6 +18,8 @@
 package android.util.jar;
 
 import android.util.apk.ApkSignatureSchemeV2Verifier;
+import android.util.apk.ApkSignatureSchemeV3Verifier;
+
 import java.io.IOException;
 import java.io.OutputStream;
 import java.nio.charset.StandardCharsets;
@@ -36,6 +38,7 @@
 import java.util.StringTokenizer;
 import java.util.jar.Attributes;
 import java.util.jar.JarFile;
+
 import sun.security.jca.Providers;
 import sun.security.pkcs.PKCS7;
 import sun.security.pkcs.SignerInfo;
@@ -56,6 +59,15 @@
  */
 class StrictJarVerifier {
     /**
+     * {@code .SF} file header section attribute indicating that the APK is signed not just with
+     * JAR signature scheme but also with APK Signature Scheme v2 or newer. This attribute
+     * facilitates v2 signature stripping detection.
+     *
+     * <p>The attribute contains a comma-separated set of signature scheme IDs.
+     */
+    private static final String SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME = "X-Android-APK-Signed";
+
+    /**
      * List of accepted digest algorithms. This list is in order from most
      * preferred to least preferred.
      */
@@ -373,17 +385,17 @@
             return;
         }
 
-        // If requested, check whether APK Signature Scheme v2 signature was stripped.
+        // If requested, check whether a newer APK Signature Scheme signature was stripped.
         if (signatureSchemeRollbackProtectionsEnforced) {
             String apkSignatureSchemeIdList =
-                    attributes.getValue(
-                            ApkSignatureSchemeV2Verifier.SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME);
+                    attributes.getValue(SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME);
             if (apkSignatureSchemeIdList != null) {
                 // This field contains a comma-separated list of APK signature scheme IDs which
                 // were used to sign this APK. If an ID is known to us, it means signatures of that
                 // scheme were stripped from the APK because otherwise we wouldn't have fallen back
                 // to verifying the APK using the JAR signature scheme.
                 boolean v2SignatureGenerated = false;
+                boolean v3SignatureGenerated = false;
                 StringTokenizer tokenizer = new StringTokenizer(apkSignatureSchemeIdList, ",");
                 while (tokenizer.hasMoreTokens()) {
                     String idText = tokenizer.nextToken().trim();
@@ -402,6 +414,12 @@
                         v2SignatureGenerated = true;
                         break;
                     }
+                    if (id == ApkSignatureSchemeV3Verifier.SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID) {
+                        // This APK was supposed to be signed with APK Signature Scheme v3 but no
+                        // such signature was found.
+                        v3SignatureGenerated = true;
+                        break;
+                    }
                 }
 
                 if (v2SignatureGenerated) {
@@ -409,6 +427,11 @@
                             + " is signed using APK Signature Scheme v2, but no such signature was"
                             + " found. Signature stripped?");
                 }
+                if (v3SignatureGenerated) {
+                    throw new SecurityException(signatureFile + " indicates " + jarName
+                            + " is signed using APK Signature Scheme v3, but no such signature was"
+                            + " found. Signature stripped?");
+                }
             }
         }
 
diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java
index bd48f45..26dfcc2 100644
--- a/core/java/android/widget/Magnifier.java
+++ b/core/java/android/widget/Magnifier.java
@@ -125,7 +125,7 @@
                 mView.getWidth() - mBitmap.getWidth()));
         final int startY = mCenterZoomCoords.y - mBitmap.getHeight() / 2;
 
-        if (startX != mPrevStartCoordsInSurface.x || startY != mPrevStartCoordsInSurface.y) {
+        if (xPosInView != mPrevPosInView.x || yPosInView != mPrevPosInView.y) {
             performPixelCopy(startX, startY);
 
             mPrevPosInView.x = xPosInView;
diff --git a/core/java/com/android/internal/widget/ButtonBarLayout.java b/core/java/com/android/internal/widget/ButtonBarLayout.java
index 82affe2..ab8be33 100644
--- a/core/java/com/android/internal/widget/ButtonBarLayout.java
+++ b/core/java/com/android/internal/widget/ButtonBarLayout.java
@@ -30,9 +30,6 @@
  * orientation when it can't fit its child views horizontally.
  */
 public class ButtonBarLayout extends LinearLayout {
-    /** Minimum screen height required for button stacking. */
-    private static final int ALLOW_STACKING_MIN_HEIGHT_DP = 320;
-
     /** Amount of the second button to "peek" above the fold when stacked. */
     private static final int PEEK_BUTTON_DP = 16;
 
@@ -46,12 +43,8 @@
     public ButtonBarLayout(Context context, AttributeSet attrs) {
         super(context, attrs);
 
-        final boolean allowStackingDefault =
-                context.getResources().getConfiguration().screenHeightDp
-                        >= ALLOW_STACKING_MIN_HEIGHT_DP;
         final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ButtonBarLayout);
-        mAllowStacking = ta.getBoolean(R.styleable.ButtonBarLayout_allowStacking,
-                allowStackingDefault);
+        mAllowStacking = ta.getBoolean(R.styleable.ButtonBarLayout_allowStacking, true);
         ta.recycle();
     }
 
diff --git a/core/proto/android/os/batterytype.proto b/core/proto/android/os/batterytype.proto
new file mode 100644
index 0000000..75d0dd3
--- /dev/null
+++ b/core/proto/android/os/batterytype.proto
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+syntax = "proto2";
+
+package android.os;
+
+option java_multiple_files = true;
+
+message BatteryTypeProto {
+   optional string type = 1;
+}
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index ac8f26d..09c08a9 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -18,6 +18,7 @@
 option java_multiple_files = true;
 option java_outer_classname = "IncidentProtoMetadata";
 
+import "frameworks/base/core/proto/android/os/batterytype.proto";
 import "frameworks/base/core/proto/android/os/cpufreq.proto";
 import "frameworks/base/core/proto/android/os/cpuinfo.proto";
 import "frameworks/base/core/proto/android/os/incidentheader.proto";
@@ -94,6 +95,11 @@
         (section).args = "/sys/devices/system/cpu/cpufreq/all_time_in_state"
     ];
 
+    optional BatteryTypeProto battery_type = 2006 [
+        (section).type = SECTION_FILE,
+        (section).args = "/sys/class/power_supply/bms/battery_type"
+    ];
+
     // System Services
     optional com.android.server.fingerprint.FingerprintServiceDumpProto fingerprint = 3000 [
         (section).type = SECTION_DUMPSYS,
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index eef9866..fc86500 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -525,7 +525,8 @@
                  Settings.Secure.VOICE_INTERACTION_SERVICE,
                  Settings.Secure.VOICE_RECOGNITION_SERVICE,
                  Settings.Secure.INSTANT_APPS_ENABLED,
-                 Settings.Secure.BACKUP_MANAGER_CONSTANTS);
+                 Settings.Secure.BACKUP_MANAGER_CONSTANTS,
+                 Settings.Secure.KEYGUARD_SLICE_URI);
 
     @Test
     public void systemSettingsBackedUpOrBlacklisted() {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index f4ec936..bef2bcb 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -1710,18 +1710,9 @@
     }
 
     private List<String> getSettingsNamesLocked(int settingsType, int userId) {
-        boolean instantApp;
-        if (UserHandle.getAppId(Binder.getCallingUid()) < Process.FIRST_APPLICATION_UID) {
-            instantApp = false;
-        } else {
-            ApplicationInfo ai = getCallingApplicationInfoOrThrow();
-            instantApp = ai.isInstantApp();
-        }
-        if (instantApp) {
-            return new ArrayList<String>(getInstantAppAccessibleSettings(settingsType));
-        } else {
-            return mSettingsRegistry.getSettingsNamesLocked(settingsType, userId);
-        }
+        // Don't enforce the instant app whitelist for now -- its too prone to unintended breakage
+        // in the current form.
+        return mSettingsRegistry.getSettingsNamesLocked(settingsType, userId);
     }
 
     private void enforceSettingReadable(String settingName, int settingsType, int userId) {
@@ -1734,8 +1725,10 @@
         }
         if (!getInstantAppAccessibleSettings(settingsType).contains(settingName)
                 && !getOverlayInstantAppAccessibleSettings(settingsType).contains(settingName)) {
-            throw new SecurityException("Setting " + settingName + " is not accessible from"
-                    + " ephemeral package " + getCallingPackage());
+            // Don't enforce the instant app whitelist for now -- its too prone to unintended
+            // breakage in the current form.
+            Slog.w(LOG_TAG, "Instant App " + ai.packageName
+                    + " trying to access unexposed setting, this will be an error in the future.");
         }
     }
 
diff --git a/packages/SystemUI/Android.mk b/packages/SystemUI/Android.mk
index 2c5eb27..73fcdd7 100644
--- a/packages/SystemUI/Android.mk
+++ b/packages/SystemUI/Android.mk
@@ -39,7 +39,12 @@
     android-support-v7-mediarouter \
     android-support-v7-palette \
     android-support-v14-preference \
-    android-support-v17-leanback
+    android-support-v17-leanback \
+    android-slices-core \
+    android-slices-view \
+    android-slices-builders \
+    apptoolkit-arch-core-runtime \
+    apptoolkit-lifecycle-extensions \
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     SystemUI-tags \
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
index 020cfee..b154d46 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
@@ -24,31 +24,20 @@
     android:layout_marginEnd="16dp"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
-    android:layout_marginTop="@dimen/date_owner_info_margin"
     android:layout_gravity="center_horizontal"
-    android:paddingTop="4dp"
     android:clipToPadding="false"
     android:orientation="vertical"
     android:layout_centerHorizontal="true">
     <TextView android:id="@+id/title"
               android:layout_width="match_parent"
               android:layout_height="wrap_content"
-              android:singleLine="true"
-              android:ellipsize="end"
-              android:fadingEdge="horizontal"
-              android:gravity="center"
-              android:textSize="22sp"
-              android:textColor="?attr/wallpaperTextColor"
+              android:layout_marginBottom="@dimen/widget_vertical_padding"
+              android:theme="@style/TextAppearance.Keyguard"
     />
-    <TextView android:id="@+id/text"
+    <LinearLayout android:id="@+id/row"
               android:layout_width="match_parent"
               android:layout_height="wrap_content"
-              android:singleLine="true"
+              android:orientation="horizontal"
               android:gravity="center"
-              android:visibility="gone"
-              android:textSize="16sp"
-              android:textColor="?attr/wallpaperTextColor"
-              android:layout_marginTop="4dp"
-              android:ellipsize="end"
     />
 </com.android.keyguard.KeyguardSliceView>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
index 138733e..c97cfc4 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
@@ -34,6 +34,7 @@
         android:orientation="vertical">
         <RelativeLayout
             android:id="@+id/keyguard_clock_container"
+            android:animateLayoutChanges="true"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_gravity="center_horizontal|top">
@@ -59,14 +60,23 @@
                 android:layout_toEndOf="@id/clock_view"
                 android:visibility="invisible"
                 android:src="@drawable/ic_aod_charging_24dp"
-                android:contentDescription="@string/accessibility_ambient_display_charging"
-            />
+                android:contentDescription="@string/accessibility_ambient_display_charging" />
+            <View
+                android:id="@+id/clock_separator"
+                android:layout_width="16dp"
+                android:layout_height="1dp"
+                android:layout_marginTop="10dp"
+                android:layout_below="@id/clock_view"
+                android:background="#f00"
+                android:layout_centerHorizontal="true" />
 
             <include layout="@layout/keyguard_status_area"
                 android:id="@+id/keyguard_status_area"
+                android:layout_marginTop="10dp"
+                android:layout_marginBottom="@dimen/widget_vertical_padding"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:layout_below="@id/clock_view" />
+                android:layout_below="@id/clock_separator" />
         </RelativeLayout>
 
         <TextView
@@ -83,6 +93,5 @@
             android:letterSpacing="0.05"
             android:ellipsize="marquee"
             android:singleLine="true" />
-
     </LinearLayout>
 </com.android.keyguard.KeyguardStatusView>
diff --git a/packages/SystemUI/res-keyguard/values-h560dp/dimens.xml b/packages/SystemUI/res-keyguard/values-h560dp/dimens.xml
index 1b6fa4c..3fb86d0 100644
--- a/packages/SystemUI/res-keyguard/values-h560dp/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values-h560dp/dimens.xml
@@ -16,5 +16,5 @@
   -->
 
 <resources>
-    <dimen name="widget_big_font_size">72dp</dimen>
+    <dimen name="widget_big_font_size">64dp</dimen>
 </resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/values-h650dp/dimens.xml b/packages/SystemUI/res-keyguard/values-h650dp/dimens.xml
index 1b6fa4c..3fb86d0 100644
--- a/packages/SystemUI/res-keyguard/values-h650dp/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values-h650dp/dimens.xml
@@ -16,5 +16,5 @@
   -->
 
 <resources>
-    <dimen name="widget_big_font_size">72dp</dimen>
+    <dimen name="widget_big_font_size">64dp</dimen>
 </resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index bcac072..463af61 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -42,9 +42,22 @@
     <dimen name="eca_overlap">-10dip</dimen>
 
     <!-- Default clock parameters -->
-    <dimen name="bottom_text_spacing_digital">-1dp</dimen>
-    <dimen name="widget_label_font_size">14sp</dimen>
-    <dimen name="widget_big_font_size">72dp</dimen>
+    <dimen name="bottom_text_spacing_digital">-10dp</dimen>
+    <!-- Slice header -->
+    <dimen name="widget_title_font_size">28sp</dimen>
+    <!-- Slice subtitle  -->
+    <dimen name="widget_label_font_size">16sp</dimen>
+    <!-- Clock without header -->
+    <dimen name="widget_big_font_size">64dp</dimen>
+    <!-- Clock with header -->
+    <dimen name="widget_small_font_size">22dp</dimen>
+    <!-- Dash between clock and header -->
+    <dimen name="widget_vertical_padding">16dp</dimen>
+    <!-- Subtitle paddings -->
+    <dimen name="widget_separator_thickness">2dp</dimen>
+    <dimen name="widget_horizontal_padding">8dp</dimen>
+    <dimen name="widget_icon_size">16dp</dimen>
+    <dimen name="widget_icon_padding">4dp</dimen>
 
     <!-- The y translation to apply at the start in appear animations. -->
     <dimen name="appear_y_translation_start">32dp</dimen>
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index 826e3ea..d50bab5 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -78,4 +78,18 @@
         <item name="wallpaperTextColorSecondary">@*android:color/secondary_text_material_dark</item>
     </style>
 
+    <style name="TextAppearance.Keyguard" parent="Theme.SystemUI">
+        <item name="android:textSize">@dimen/widget_title_font_size</item>
+        <item name="android:gravity">center</item>
+        <item name="android:ellipsize">end</item>
+        <item name="android:maxLines">2</item>
+    </style>
+
+    <style name="TextAppearance.Keyguard.Secondary">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:textSize">@dimen/widget_label_font_size</item>
+        <item name="android:singleLine">true</item>
+    </style>
+
 </resources>
diff --git a/packages/SystemUI/res/layout/pip_menu_activity.xml b/packages/SystemUI/res/layout/pip_menu_activity.xml
index 8b7f692..03d587b 100644
--- a/packages/SystemUI/res/layout/pip_menu_activity.xml
+++ b/packages/SystemUI/res/layout/pip_menu_activity.xml
@@ -60,14 +60,24 @@
       </FrameLayout>
   </FrameLayout>
 
-  <ImageView
-      android:id="@+id/dismiss"
-      android:layout_width="@dimen/pip_action_size"
-      android:layout_height="@dimen/pip_action_size"
-      android:layout_gravity="top|end"
-      android:padding="@dimen/pip_action_padding"
-      android:contentDescription="@string/pip_phone_close"
-      android:src="@drawable/ic_close_white"
-      android:background="?android:selectableItemBackgroundBorderless" />
+    <ImageView
+        android:id="@+id/settings"
+        android:layout_width="@dimen/pip_action_size"
+        android:layout_height="@dimen/pip_action_size"
+        android:layout_gravity="top|start"
+        android:padding="@dimen/pip_action_padding"
+        android:contentDescription="@string/pip_phone_settings"
+        android:src="@drawable/ic_settings"
+        android:background="?android:selectableItemBackgroundBorderless" />
+
+    <ImageView
+        android:id="@+id/dismiss"
+        android:layout_width="@dimen/pip_action_size"
+        android:layout_height="@dimen/pip_action_size"
+        android:layout_gravity="top|end"
+        android:padding="@dimen/pip_action_padding"
+        android:contentDescription="@string/pip_phone_close"
+        android:src="@drawable/ic_close_white"
+        android:background="?android:selectableItemBackgroundBorderless" />
 
 </FrameLayout>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index fd205dd..98537a1 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1895,6 +1895,9 @@
     <!-- Label for PIP close button [CHAR LIMIT=NONE]-->
     <string name="pip_phone_close">Close</string>
 
+    <!-- Label for PIP settings button [CHAR LIMIT=NONE]-->
+    <string name="pip_phone_settings">Settings</string>
+
     <!-- Label for PIP the drag to dismiss hint [CHAR LIMIT=NONE]-->
     <string name="pip_phone_dismiss_hint">Drag down to dismiss</string>
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
index cb3d59c..b9bf80d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -17,38 +17,56 @@
 package com.android.keyguard;
 
 import android.app.PendingIntent;
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
-import android.app.slice.SliceQuery;
+import android.arch.lifecycle.LiveData;
+import android.arch.lifecycle.Observer;
 import android.content.Context;
-import android.database.ContentObserver;
+import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.drawable.Drawable;
 import android.net.Uri;
-import android.os.Handler;
+import android.provider.Settings;
 import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
 import com.android.internal.graphics.ColorUtils;
+import com.android.settingslib.Utils;
+import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.keyguard.KeyguardSliceProvider;
+import com.android.systemui.tuner.TunerService;
 
-import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.function.Consumer;
+
+import androidx.app.slice.Slice;
+import androidx.app.slice.SliceItem;
+import androidx.app.slice.core.SliceQuery;
+import androidx.app.slice.widget.SliceLiveData;
 
 /**
  * View visible under the clock on the lock screen and AoD.
  */
-public class KeyguardSliceView extends LinearLayout {
+public class KeyguardSliceView extends LinearLayout implements View.OnClickListener,
+        Observer<Slice>, TunerService.Tunable {
 
-    private final Uri mKeyguardSliceUri;
+    private static final String TAG = "KeyguardSliceView";
+    private final HashMap<View, PendingIntent> mClickActions;
+    private Uri mKeyguardSliceUri;
     private TextView mTitle;
-    private TextView mText;
-    private Slice mSlice;
-    private PendingIntent mSliceAction;
+    private LinearLayout mRow;
     private int mTextColor;
     private float mDarkAmount = 0;
 
-    private final ContentObserver mObserver;
+    private LiveData<Slice> mLiveData;
+    private int mIconSize;
+    private Consumer<Boolean> mListener;
+    private boolean mHasHeader;
 
     public KeyguardSliceView(Context context) {
         this(context, null, 0);
@@ -60,16 +78,20 @@
 
     public KeyguardSliceView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
-        mObserver = new KeyguardSliceObserver(new Handler());
-        mKeyguardSliceUri = Uri.parse(KeyguardSliceProvider.KEYGUARD_SLICE_URI);;
+
+        TunerService tunerService = Dependency.get(TunerService.class);
+        tunerService.addTunable(this, Settings.Secure.KEYGUARD_SLICE_URI);
+
+        mClickActions = new HashMap<>();
     }
 
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
         mTitle = findViewById(R.id.title);
-        mText = findViewById(R.id.text);
-        mTextColor = mTitle.getCurrentTextColor();
+        mRow = findViewById(R.id.row);
+        mTextColor = Utils.getColorAttr(mContext, R.attr.wallpaperTextColor);
+        mIconSize = (int) mContext.getResources().getDimension(R.dimen.widget_icon_size);
     }
 
     @Override
@@ -77,57 +99,103 @@
         super.onAttachedToWindow();
 
         // Set initial content
-        showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri,
-                Collections.emptyList()));
+        showSlice(Slice.bindSlice(getContext(), mKeyguardSliceUri));
 
         // Make sure we always have the most current slice
-        getContext().getContentResolver().registerContentObserver(mKeyguardSliceUri,
-                false /* notifyDescendants */, mObserver);
+        mLiveData.observeForever(this);
     }
 
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
 
-        getContext().getContentResolver().unregisterContentObserver(mObserver);
+        mLiveData.removeObserver(this);
     }
 
     private void showSlice(Slice slice) {
-        // Items will be wrapped into an action when they have tap targets.
-        SliceItem actionSlice = SliceQuery.find(slice, SliceItem.FORMAT_ACTION);
-        if (actionSlice != null) {
-            mSlice = actionSlice.getSlice();
-            mSliceAction = actionSlice.getAction();
-        } else {
-            mSlice = slice;
-            mSliceAction = null;
-        }
 
-        if (mSlice == null) {
-            setVisibility(GONE);
-            return;
-        }
+        // Main area
+        SliceItem mainItem = SliceQuery.find(slice, android.app.slice.SliceItem.FORMAT_SLICE,
+                null /* hints */, new String[]{android.app.slice.Slice.HINT_LIST_ITEM});
+        mHasHeader = mainItem != null;
 
-        SliceItem title = SliceQuery.find(mSlice, SliceItem.FORMAT_TEXT, Slice.HINT_TITLE, null);
-        if (title == null) {
+        List<SliceItem> subItems = SliceQuery.findAll(slice,
+                android.app.slice.SliceItem.FORMAT_SLICE,
+                new String[]{android.app.slice.Slice.HINT_LIST_ITEM},
+                null /* nonHints */);
+
+        if (!mHasHeader) {
             mTitle.setVisibility(GONE);
         } else {
             mTitle.setVisibility(VISIBLE);
-            mTitle.setText(title.getText());
+            SliceItem mainTitle = SliceQuery.find(mainItem.getSlice(),
+                    android.app.slice.SliceItem.FORMAT_TEXT,
+                    new String[]{android.app.slice.Slice.HINT_TITLE},
+                    null /* nonHints */);
+            mTitle.setText(mainTitle.getText());
         }
 
-        SliceItem text = SliceQuery.find(mSlice, SliceItem.FORMAT_TEXT, null, Slice.HINT_TITLE);
-        if (text == null) {
-            mText.setVisibility(GONE);
-        } else {
-            mText.setVisibility(VISIBLE);
-            mText.setText(text.getText());
+        mClickActions.clear();
+        final int subItemsCount = subItems.size();
+
+        for (int i = 0; i < subItemsCount; i++) {
+            SliceItem item = subItems.get(i);
+            final Uri itemTag = item.getSlice().getUri();
+            // Try to reuse the view if already exists in the layout
+            KeyguardSliceButton button = mRow.findViewWithTag(itemTag);
+            if (button == null) {
+                button = new KeyguardSliceButton(mContext);
+                button.setTextColor(mTextColor);
+                button.setTag(itemTag);
+            } else {
+                mRow.removeView(button);
+            }
+            button.setHasDivider(i < subItemsCount - 1);
+            mRow.addView(button, i);
+
+            PendingIntent pendingIntent;
+            try {
+                pendingIntent = item.getAction();
+            } catch (RuntimeException e) {
+                Log.w(TAG, "Cannot retrieve action from keyguard slice", e);
+                pendingIntent = null;
+            }
+            mClickActions.put(button, pendingIntent);
+
+            SliceItem title = SliceQuery.find(item.getSlice(),
+                    android.app.slice.SliceItem.FORMAT_TEXT,
+                    new String[]{android.app.slice.Slice.HINT_TITLE},
+                    null /* nonHints */);
+            button.setText(title.getText());
+
+            Drawable iconDrawable = null;
+            SliceItem icon = SliceQuery.find(item.getSlice(),
+                    android.app.slice.SliceItem.FORMAT_IMAGE);
+            if (icon != null) {
+                iconDrawable = icon.getIcon().loadDrawable(mContext);
+                final int width = (int) (iconDrawable.getIntrinsicWidth()
+                        / (float) iconDrawable.getIntrinsicHeight() * mIconSize);
+                iconDrawable.setBounds(0, 0, Math.max(width, 1), mIconSize);
+            }
+            button.setCompoundDrawablesRelative(iconDrawable, null, null, null);
+            button.setOnClickListener(this);
         }
 
-        final int visibility = title == null && text == null ? GONE : VISIBLE;
+        // Removing old views
+        for (int i = 0; i < mRow.getChildCount(); i++) {
+            View child = mRow.getChildAt(i);
+            if (!mClickActions.containsKey(child)) {
+                mRow.removeView(child);
+                i--;
+            }
+        }
+
+        final int visibility = mHasHeader || subItemsCount > 0 ? VISIBLE : GONE;
         if (visibility != getVisibility()) {
             setVisibility(visibility);
         }
+
+        mListener.accept(mHasHeader);
     }
 
     public void setDark(float darkAmount) {
@@ -135,30 +203,113 @@
         updateTextColors();
     }
 
-    public void setTextColor(int textColor) {
-        mTextColor = textColor;
-    }
-
     private void updateTextColors() {
         final int blendedColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, mDarkAmount);
         mTitle.setTextColor(blendedColor);
-        mText.setTextColor(blendedColor);
+        int childCount = mRow.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View v = mRow.getChildAt(i);
+            if (v instanceof Button) {
+                ((Button) v).setTextColor(blendedColor);
+            }
+        }
     }
 
-    private class KeyguardSliceObserver extends ContentObserver {
-        KeyguardSliceObserver(Handler handler) {
-            super(handler);
+    @Override
+    public void onClick(View v) {
+        final PendingIntent action = mClickActions.get(v);
+        if (action != null) {
+            try {
+                action.send();
+            } catch (PendingIntent.CanceledException e) {
+                Log.i(TAG, "Pending intent cancelled, nothing to launch", e);
+            }
+        }
+    }
+
+    public void setListener(Consumer<Boolean> listener) {
+        mListener = listener;
+    }
+
+    public boolean hasHeader() {
+        return mHasHeader;
+    }
+
+    /**
+     * LiveData observer lifecycle.
+     * @param slice the new slice content.
+     */
+    @Override
+    public void onChanged(Slice slice) {
+        showSlice(slice);
+    }
+
+    @Override
+    public void onTuningChanged(String key, String newValue) {
+        setupUri(newValue);
+    }
+
+    public void setupUri(String uriString) {
+        if (uriString == null) {
+            uriString = KeyguardSliceProvider.KEYGUARD_SLICE_URI;
+        }
+
+        boolean wasObserving = false;
+        if (mLiveData != null && mLiveData.hasActiveObservers()) {
+            wasObserving = true;
+            mLiveData.removeObserver(this);
+        }
+
+        mKeyguardSliceUri = Uri.parse(uriString);
+        mLiveData = SliceLiveData.fromUri(mContext, mKeyguardSliceUri);
+
+        if (wasObserving) {
+            mLiveData.observeForever(this);
+            showSlice(Slice.bindSlice(getContext(), mKeyguardSliceUri));
+        }
+    }
+
+    /**
+     * Representation of an item that appears under the clock on main keyguard message.
+     * Shows optional separator.
+     */
+    private class KeyguardSliceButton extends Button {
+
+        private final Paint mPaint;
+        private boolean mHasDivider;
+
+        public KeyguardSliceButton(Context context) {
+            super(context, null /* attrs */,
+                    com.android.keyguard.R.style.TextAppearance_Keyguard_Secondary);
+            mPaint = new Paint();
+            mPaint.setStyle(Paint.Style.STROKE);
+            float dividerWidth = context.getResources()
+                    .getDimension(R.dimen.widget_separator_thickness);
+            mPaint.setStrokeWidth(dividerWidth);
+            int horizontalPadding = (int) context.getResources()
+                    .getDimension(R.dimen.widget_horizontal_padding);
+            setPadding(horizontalPadding, 0, horizontalPadding, 0);
+            setCompoundDrawablePadding((int) context.getResources()
+                    .getDimension(R.dimen.widget_icon_padding));
+        }
+
+        public void setHasDivider(boolean hasDivider) {
+            mHasDivider = hasDivider;
         }
 
         @Override
-        public void onChange(boolean selfChange) {
-            this.onChange(selfChange, null);
+        public void setTextColor(int color) {
+            super.setTextColor(color);
+            mPaint.setColor(color);
         }
 
         @Override
-        public void onChange(boolean selfChange, Uri uri) {
-            showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri,
-                    Collections.emptyList()));
+        protected void onDraw(Canvas canvas) {
+            super.onDraw(canvas);
+            if (mHasDivider) {
+                final int lineX = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? 0 : getWidth();
+                canvas.drawLine(lineX, 0, lineX, getHeight(), mPaint);
+            }
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index 78cf2b9..4abf886 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -28,6 +28,7 @@
 import android.support.v4.graphics.ColorUtils;
 import android.text.TextUtils;
 import android.text.format.DateFormat;
+import android.util.ArraySet;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.Slog;
@@ -38,11 +39,11 @@
 import android.widget.TextClock;
 import android.widget.TextView;
 
-import com.android.internal.util.ArrayUtils;
 import com.android.internal.widget.LockPatternUtils;
-import com.android.settingslib.Utils;
 import com.android.systemui.ChargingView;
 
+import com.google.android.collect.Sets;
+
 import java.util.Locale;
 
 public class KeyguardStatusView extends GridLayout {
@@ -52,8 +53,11 @@
 
     private final LockPatternUtils mLockPatternUtils;
     private final AlarmManager mAlarmManager;
+    private final float mSmallClockScale;
+    private final float mWidgetPadding;
 
     private TextClock mClockView;
+    private View mClockSeparator;
     private TextView mOwnerInfo;
     private ViewGroup mClockContainer;
     private ChargingView mBatteryDoze;
@@ -61,7 +65,7 @@
     private Runnable mPendingMarqueeStart;
     private Handler mHandler;
 
-    private View[] mVisibleInDoze;
+    private ArraySet<View> mVisibleInDoze;
     private boolean mPulsing;
     private float mDarkAmount = 0;
     private int mTextColor;
@@ -112,6 +116,9 @@
         mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
         mLockPatternUtils = new LockPatternUtils(getContext());
         mHandler = new Handler(Looper.myLooper());
+        mSmallClockScale = getResources().getDimension(R.dimen.widget_small_font_size)
+                / getResources().getDimension(R.dimen.widget_big_font_size);
+        mWidgetPadding = getResources().getDimension(R.dimen.widget_vertical_padding);
     }
 
     private void setEnableMarquee(boolean enabled) {
@@ -150,9 +157,14 @@
         mOwnerInfo = findViewById(R.id.owner_info);
         mBatteryDoze = findViewById(R.id.battery_doze);
         mKeyguardSlice = findViewById(R.id.keyguard_status_area);
-        mVisibleInDoze = new View[]{mBatteryDoze, mClockView, mKeyguardSlice};
+        mClockSeparator = findViewById(R.id.clock_separator);
+        mVisibleInDoze = Sets.newArraySet(mBatteryDoze, mClockView, mKeyguardSlice,
+                mClockSeparator);
         mTextColor = mClockView.getCurrentTextColor();
 
+        mKeyguardSlice.setListener(this::onSliceContentChanged);
+        onSliceContentChanged(mKeyguardSlice.hasHeader());
+
         boolean shouldMarquee = KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive();
         setEnableMarquee(shouldMarquee);
         refresh();
@@ -163,6 +175,22 @@
         mClockView.setElegantTextHeight(false);
     }
 
+    private void onSliceContentChanged(boolean hasHeader) {
+        final float clockScale = hasHeader ? mSmallClockScale : 1;
+        float translation = (mClockView.getHeight() - (mClockView.getHeight() * clockScale)) / 2f;
+        if (hasHeader) {
+            translation -= mWidgetPadding;
+        }
+        mClockView.setTranslationY(translation);
+        mClockView.setScaleX(clockScale);
+        mClockView.setScaleY(clockScale);
+        final float batteryTranslation =
+                -(mClockView.getWidth() - (mClockView.getWidth() * clockScale)) / 2;
+        mBatteryDoze.setTranslationX(batteryTranslation);
+        mBatteryDoze.setTranslationY(translation);
+        mClockSeparator.setVisibility(hasHeader ? VISIBLE : GONE);
+    }
+
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
@@ -303,7 +331,7 @@
         final int N = mClockContainer.getChildCount();
         for (int i = 0; i < N; i++) {
             View child = mClockContainer.getChildAt(i);
-            if (ArrayUtils.contains(mVisibleInDoze, child)) {
+            if (mVisibleInDoze.contains(child)) {
                 continue;
             }
             child.setAlpha(dark ? 0 : 1);
@@ -312,10 +340,12 @@
             mOwnerInfo.setAlpha(dark ? 0 : 1);
         }
 
+        final int blendedTextColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, darkAmount);
         updateDozeVisibleViews();
         mBatteryDoze.setDark(dark);
         mKeyguardSlice.setDark(darkAmount);
-        mClockView.setTextColor(ColorUtils.blendARGB(mTextColor, Color.WHITE, darkAmount));
+        mClockView.setTextColor(blendedTextColor);
+        mClockSeparator.setBackgroundColor(blendedTextColor);
     }
 
     public void setPulsing(boolean pulsing) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index 6ddc76b..2b09903 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -24,8 +24,6 @@
 import android.icu.text.DisplayContext;
 import android.net.Uri;
 import android.os.Handler;
-import android.app.slice.Slice;
-import android.app.slice.SliceProvider;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.R;
@@ -33,15 +31,22 @@
 import java.util.Date;
 import java.util.Locale;
 
+import androidx.app.slice.Slice;
+import androidx.app.slice.SliceProvider;
+import androidx.app.slice.builders.ListBuilder;
+import androidx.app.slice.builders.ListBuilder.RowBuilder;
+
 /**
  * Simple Slice provider that shows the current date.
  */
 public class KeyguardSliceProvider extends SliceProvider {
 
     public static final String KEYGUARD_SLICE_URI = "content://com.android.systemui.keyguard/main";
+    public static final String KEYGUARD_DATE_URI = "content://com.android.systemui.keyguard/date";
 
     private final Date mCurrentTime = new Date();
     protected final Uri mSliceUri;
+    protected final Uri mDateUri;
     private final Handler mHandler;
     private String mDatePattern;
     private DateFormat mDateFormat;
@@ -80,23 +85,31 @@
     KeyguardSliceProvider(Handler handler) {
         mHandler = handler;
         mSliceUri = Uri.parse(KEYGUARD_SLICE_URI);
+        mDateUri = Uri.parse(KEYGUARD_DATE_URI);
     }
 
+
+
     @Override
     public Slice onBindSlice(Uri sliceUri) {
-        return new Slice.Builder(sliceUri).addText(mLastText, null, Slice.HINT_TITLE).build();
+        return new ListBuilder(mSliceUri)
+                .addRow(new RowBuilder(mDateUri).setTitle(mLastText)).build();
     }
 
     @Override
-    public boolean onCreate() {
-
+    public boolean onCreateSliceProvider() {
         mDatePattern = getContext().getString(R.string.system_ui_date_pattern);
-
         registerClockUpdate(false /* everyMinute */);
         updateClock();
         return true;
     }
 
+    /**
+     * Registers a broadcast receiver for clock updates, include date, time zone and manually
+     * changing the date/time via the settings app.
+     *
+     * @param everyMinute {@code true} if you also want updates every minute.
+     */
     protected void registerClockUpdate(boolean everyMinute) {
         if (mRegistered) {
             if (mRegisteredEveryMinute == everyMinute) {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
index db999c4..dce3e24 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -64,7 +64,6 @@
     private InputConsumerController mInputConsumerController;
     private PipMenuActivityController mMenuController;
     private PipMediaController mMediaController;
-    private PipNotificationController mNotificationController;
     private PipTouchHandler mTouchHandler;
 
     /**
@@ -76,8 +75,6 @@
             mTouchHandler.onActivityPinned();
             mMediaController.onActivityPinned();
             mMenuController.onActivityPinned();
-            mNotificationController.onActivityPinned(packageName, userId,
-                    true /* deferUntilAnimationEnds */);
 
             SystemServicesProxy.getInstance(mContext).setPipVisibility(true);
         }
@@ -90,7 +87,6 @@
             final int userId = topActivity != null ? topPipActivityInfo.second : 0;
             mMenuController.onActivityUnpinned();
             mTouchHandler.onActivityUnpinned(topActivity);
-            mNotificationController.onActivityUnpinned(topActivity, userId);
 
             SystemServicesProxy.getInstance(mContext).setPipVisibility(topActivity != null);
         }
@@ -107,7 +103,6 @@
             mTouchHandler.setTouchEnabled(true);
             mTouchHandler.onPinnedStackAnimationEnded();
             mMenuController.onPinnedStackAnimationEnded();
-            mNotificationController.onPinnedStackAnimationEnded();
         }
 
         @Override
@@ -182,8 +177,6 @@
                 mInputConsumerController);
         mTouchHandler = new PipTouchHandler(context, mActivityManager, mMenuController,
                 mInputConsumerController);
-        mNotificationController = new PipNotificationController(context, mActivityManager,
-                mTouchHandler.getMotionHelper());
         EventBus.getDefault().register(this);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
index 90f7b8d..bfe07a9 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
@@ -16,6 +16,10 @@
 
 package com.android.systemui.pip.phone;
 
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.provider.Settings.ACTION_PICTURE_IN_PICTURE_SETTINGS;
+
 import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_ACTIONS;
 import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_ALLOW_TIMEOUT;
 import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_CONTROLLER_MESSENGER;
@@ -39,6 +43,7 @@
 import android.app.ActivityManager;
 import android.app.PendingIntent.CanceledException;
 import android.app.RemoteAction;
+import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ParceledListSlice;
 import android.graphics.Color;
@@ -46,12 +51,15 @@
 import android.graphics.Rect;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
 import android.os.Messenger;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.util.Log;
+import android.util.Pair;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
@@ -105,6 +113,7 @@
     private Drawable mBackgroundDrawable;
     private View mMenuContainer;
     private LinearLayout mActionsGroup;
+    private View mSettingsButton;
     private View mDismissButton;
     private ImageView mExpandButton;
     private int mBetweenActionPaddingLand;
@@ -218,6 +227,11 @@
             }
             return true;
         });
+        mSettingsButton = findViewById(R.id.settings);
+        mSettingsButton.setAlpha(0);
+        mSettingsButton.setOnClickListener((v) -> {
+            showSettings();
+        });
         mDismissButton = findViewById(R.id.dismiss);
         mDismissButton.setAlpha(0);
         mDismissButton.setOnClickListener((v) -> {
@@ -352,12 +366,14 @@
             ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA,
                     mMenuContainer.getAlpha(), 1f);
             menuAnim.addUpdateListener(mMenuBgUpdateListener);
+            ObjectAnimator settingsAnim = ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA,
+                    mSettingsButton.getAlpha(), 1f);
             ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA,
                     mDismissButton.getAlpha(), 1f);
             if (menuState == MENU_STATE_FULL) {
-                mMenuContainerAnimator.playTogether(menuAnim, dismissAnim);
+                mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim);
             } else {
-                mMenuContainerAnimator.play(dismissAnim);
+                mMenuContainerAnimator.playTogether(settingsAnim, dismissAnim);
             }
             mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN);
             mMenuContainerAnimator.setDuration(MENU_FADE_DURATION);
@@ -394,9 +410,11 @@
             ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA,
                     mMenuContainer.getAlpha(), 0f);
             menuAnim.addUpdateListener(mMenuBgUpdateListener);
+            ObjectAnimator settingsAnim = ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA,
+                    mSettingsButton.getAlpha(), 0f);
             ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA,
                     mDismissButton.getAlpha(), 0f);
-            mMenuContainerAnimator.playTogether(menuAnim, dismissAnim);
+            mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim);
             mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_OUT);
             mMenuContainerAnimator.setDuration(MENU_FADE_DURATION);
             mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() {
@@ -526,12 +544,14 @@
         final float menuAlpha = 1 - fraction;
         if (mMenuState == MENU_STATE_FULL) {
             mMenuContainer.setAlpha(menuAlpha);
+            mSettingsButton.setAlpha(menuAlpha);
             mDismissButton.setAlpha(menuAlpha);
             final float interpolatedAlpha =
                     MENU_BACKGROUND_ALPHA * menuAlpha + DISMISS_BACKGROUND_ALPHA * fraction;
             alpha = (int) (interpolatedAlpha * 255);
         } else {
             if (mMenuState == MENU_STATE_CLOSE) {
+                mSettingsButton.setAlpha(menuAlpha);
                 mDismissButton.setAlpha(menuAlpha);
             }
             alpha = (int) (fraction * DISMISS_BACKGROUND_ALPHA * 255);
@@ -588,6 +608,19 @@
         sendMessage(m, "Could not notify controller to show PIP menu");
     }
 
+    private void showSettings() {
+        final Pair<ComponentName, Integer> topPipActivityInfo =
+                PipUtils.getTopPinnedActivity(this, ActivityManager.getService());
+        if (topPipActivityInfo.first != null) {
+            final UserHandle user = UserHandle.of(topPipActivityInfo.second);
+            final Intent settingsIntent = new Intent(ACTION_PICTURE_IN_PICTURE_SETTINGS,
+                    Uri.fromParts("package", topPipActivityInfo.first.getPackageName(), null));
+            settingsIntent.putExtra(Intent.EXTRA_USER_HANDLE, user);
+            settingsIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
+            startActivity(settingsIntent);
+        }
+    }
+
     private void notifyActivityCallback(Messenger callback) {
         Message m = Message.obtain();
         m.what = PipMenuActivityController.MESSAGE_UPDATE_ACTIVITY_CALLBACK;
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipNotificationController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipNotificationController.java
deleted file mode 100644
index 6d083e9..0000000
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipNotificationController.java
+++ /dev/null
@@ -1,231 +0,0 @@
-/*
- * Copyright (C) 2017 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 com.android.systemui.pip.phone;
-
-import static android.app.AppOpsManager.MODE_ALLOWED;
-import static android.app.AppOpsManager.OP_PICTURE_IN_PICTURE;
-import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
-import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
-import static android.provider.Settings.ACTION_PICTURE_IN_PICTURE_SETTINGS;
-
-import android.app.AppOpsManager;
-import android.app.AppOpsManager.OnOpChangedListener;
-import android.app.IActivityManager;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-import android.graphics.Canvas;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.UserHandle;
-import android.util.IconDrawableFactory;
-import android.util.Log;
-import android.util.Pair;
-
-import com.android.systemui.R;
-import com.android.systemui.SystemUI;
-import com.android.systemui.util.NotificationChannels;
-
-/**
- * Manages the BTW notification that shows whenever an activity enters or leaves picture-in-picture.
- */
-public class PipNotificationController {
-    private static final String TAG = PipNotificationController.class.getSimpleName();
-
-    private static final String NOTIFICATION_TAG = PipNotificationController.class.getName();
-    private static final int NOTIFICATION_ID = 0;
-
-    private Context mContext;
-    private IActivityManager mActivityManager;
-    private AppOpsManager mAppOpsManager;
-    private NotificationManager mNotificationManager;
-    private IconDrawableFactory mIconDrawableFactory;
-
-    private PipMotionHelper mMotionHelper;
-
-    // Used when building a deferred notification
-    private String mDeferredNotificationPackageName;
-    private int mDeferredNotificationUserId;
-
-    private AppOpsManager.OnOpChangedListener mAppOpsChangedListener = new OnOpChangedListener() {
-        @Override
-        public void onOpChanged(String op, String packageName) {
-            try {
-                // Dismiss the PiP once the user disables the app ops setting for that package
-                final Pair<ComponentName, Integer> topPipActivityInfo =
-                        PipUtils.getTopPinnedActivity(mContext, mActivityManager);
-                if (topPipActivityInfo.first != null) {
-                    final ApplicationInfo appInfo = mContext.getPackageManager()
-                            .getApplicationInfoAsUser(packageName, 0, topPipActivityInfo.second);
-                    if (appInfo.packageName.equals(topPipActivityInfo.first.getPackageName()) &&
-                                mAppOpsManager.checkOpNoThrow(OP_PICTURE_IN_PICTURE, appInfo.uid,
-                                        packageName) != MODE_ALLOWED) {
-                        mMotionHelper.dismissPip();
-                    }
-                }
-            } catch (NameNotFoundException e) {
-                // Unregister the listener if the package can't be found
-                unregisterAppOpsListener();
-            }
-        }
-    };
-
-    public PipNotificationController(Context context, IActivityManager activityManager,
-            PipMotionHelper motionHelper) {
-        mContext = context;
-        mActivityManager = activityManager;
-        mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
-        mNotificationManager = NotificationManager.from(context);
-        mMotionHelper = motionHelper;
-        mIconDrawableFactory = IconDrawableFactory.newInstance(context);
-    }
-
-    public void onActivityPinned(String packageName, int userId, boolean deferUntilAnimationEnds) {
-        // Clear any existing notification
-        mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID);
-
-        if (deferUntilAnimationEnds) {
-            mDeferredNotificationPackageName = packageName;
-            mDeferredNotificationUserId = userId;
-        } else {
-            showNotificationForApp(packageName, userId);
-        }
-
-        // Register for changes to the app ops setting for this package while it is in PiP
-        registerAppOpsListener(packageName);
-    }
-
-    public void onPinnedStackAnimationEnded() {
-        if (mDeferredNotificationPackageName != null) {
-            showNotificationForApp(mDeferredNotificationPackageName, mDeferredNotificationUserId);
-            mDeferredNotificationPackageName = null;
-            mDeferredNotificationUserId = 0;
-        }
-    }
-
-    public void onActivityUnpinned(ComponentName topPipActivity, int userId) {
-        // Unregister for changes to the previously PiP'ed package
-        unregisterAppOpsListener();
-
-        // Reset the deferred notification package
-        mDeferredNotificationPackageName = null;
-        mDeferredNotificationUserId = 0;
-
-        if (topPipActivity != null) {
-            // onActivityUnpinned() is only called after the transition is complete, so we don't
-            // need to defer until the animation ends to update the notification
-            onActivityPinned(topPipActivity.getPackageName(), userId,
-                    false /* deferUntilAnimationEnds */);
-        } else {
-            mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID);
-        }
-    }
-
-    /**
-     * Builds and shows the notification for the given app.
-     */
-    private void showNotificationForApp(String packageName, int userId) {
-        // Build a new notification
-        try {
-            final UserHandle user = UserHandle.of(userId);
-            final Context userContext = mContext.createPackageContextAsUser(
-                    mContext.getPackageName(), 0, user);
-            final Notification.Builder builder =
-                    new Notification.Builder(userContext, NotificationChannels.GENERAL)
-                            .setLocalOnly(true)
-                            .setOngoing(true)
-                            .setSmallIcon(R.drawable.pip_notification_icon)
-                            .setColor(mContext.getColor(
-                                    com.android.internal.R.color.system_notification_accent_color));
-            if (updateNotificationForApp(builder, packageName, user)) {
-                SystemUI.overrideNotificationAppName(mContext, builder);
-
-                // Show the new notification
-                mNotificationManager.notify(NOTIFICATION_TAG, NOTIFICATION_ID, builder.build());
-            }
-        } catch (NameNotFoundException e) {
-            Log.e(TAG, "Could not show notification for application", e);
-        }
-    }
-
-    /**
-     * Updates the notification builder with app-specific information, returning whether it was
-     * successful.
-     */
-    private boolean updateNotificationForApp(Notification.Builder builder, String packageName,
-            UserHandle user) throws NameNotFoundException {
-        final PackageManager pm = mContext.getPackageManager();
-        final ApplicationInfo appInfo;
-        try {
-            appInfo = pm.getApplicationInfoAsUser(packageName, 0, user.getIdentifier());
-        } catch (NameNotFoundException e) {
-            Log.e(TAG, "Could not update notification for application", e);
-            return false;
-        }
-
-        if (appInfo != null) {
-            final String appName = pm.getUserBadgedLabel(pm.getApplicationLabel(appInfo), user)
-                    .toString();
-            final String message = mContext.getString(R.string.pip_notification_message, appName);
-            final Intent settingsIntent = new Intent(ACTION_PICTURE_IN_PICTURE_SETTINGS,
-                    Uri.fromParts("package", packageName, null));
-            settingsIntent.putExtra(Intent.EXTRA_USER_HANDLE, user);
-            settingsIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
-
-            final Drawable iconDrawable = mIconDrawableFactory.getBadgedIcon(appInfo);
-            builder.setContentTitle(mContext.getString(R.string.pip_notification_title, appName))
-                    .setContentText(message)
-                    .setContentIntent(PendingIntent.getActivityAsUser(mContext, packageName.hashCode(),
-                            settingsIntent, FLAG_CANCEL_CURRENT, null, user))
-                    .setStyle(new Notification.BigTextStyle().bigText(message))
-                    .setLargeIcon(createBitmap(iconDrawable).createAshmemBitmap());
-            return true;
-        }
-        return false;
-    }
-
-    private void registerAppOpsListener(String packageName) {
-        mAppOpsManager.startWatchingMode(OP_PICTURE_IN_PICTURE, packageName,
-                mAppOpsChangedListener);
-    }
-
-    private void unregisterAppOpsListener() {
-        mAppOpsManager.stopWatchingMode(mAppOpsChangedListener);
-    }
-
-    /**
-     * Bakes a drawable into a bitmap.
-     */
-    private Bitmap createBitmap(Drawable d) {
-        Bitmap bitmap = Bitmap.createBitmap(d.getIntrinsicWidth(), d.getIntrinsicHeight(),
-                Config.ARGB_8888);
-        Canvas c = new Canvas(bitmap);
-        d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
-        d.draw(c);
-        c.setBitmap(null);
-        return bitmap;
-    }
-}
diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk
index 9d44895..066cfe5 100644
--- a/packages/SystemUI/tests/Android.mk
+++ b/packages/SystemUI/tests/Android.mk
@@ -46,7 +46,12 @@
     android-support-v7-mediarouter \
     android-support-v7-palette \
     android-support-v14-preference \
-    android-support-v17-leanback
+    android-support-v17-leanback \
+    android-slices-core \
+    android-slices-view \
+    android-slices-builders \
+    apptoolkit-arch-core-runtime \
+    apptoolkit-lifecycle-extensions \
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     metrics-helper-lib \
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
index 4eae342..be28569 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
@@ -16,11 +16,10 @@
 
 package com.android.systemui.keyguard;
 
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
-import android.app.slice.SliceQuery;
+import androidx.app.slice.Slice;
 import android.content.Intent;
 import android.net.Uri;
+import android.os.Debug;
 import android.os.Handler;
 import android.support.test.filters.SmallTest;
 import android.testing.AndroidTestingRunner;
@@ -34,6 +33,9 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import androidx.app.slice.SliceItem;
+import androidx.app.slice.core.SliceQuery;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper(setAsMainLooper = true)
@@ -63,7 +65,8 @@
     @Test
     public void returnsValidSlice() {
         Slice slice = mProvider.onBindSlice(Uri.parse(KeyguardSliceProvider.KEYGUARD_SLICE_URI));
-        SliceItem text = SliceQuery.find(slice, SliceItem.FORMAT_TEXT, Slice.HINT_TITLE,
+        SliceItem text = SliceQuery.find(slice, android.app.slice.SliceItem.FORMAT_TEXT,
+                android.app.slice.Slice.HINT_TITLE,
                 null /* nonHints */);
         Assert.assertNotNull("Slice must provide a title.", text);
     }
@@ -78,9 +81,10 @@
 
     @Test
     public void updatesClock() {
+        mProvider.mUpdateClockInvokations = 0;
         mProvider.mIntentReceiver.onReceive(getContext(), new Intent(Intent.ACTION_TIME_TICK));
         TestableLooper.get(this).processAllMessages();
-        Assert.assertEquals("Clock should have been updated.", 2 /* expected */,
+        Assert.assertEquals("Clock should have been updated.", 1 /* expected */,
                 mProvider.mUpdateClockInvokations);
     }
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 696d895..1157af4 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -163,10 +163,12 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInfoLite;
 import android.content.pm.PackageInstaller;
+import android.content.pm.PackageList;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.PackageManager.LegacyPackageDeleteObserver;
 import android.content.pm.PackageManager.PackageInfoFlags;
+import android.content.pm.PackageManagerInternal.PackageListObserver;
 import android.content.pm.PackageParser;
 import android.content.pm.PackageParser.ActivityIntentInfo;
 import android.content.pm.PackageParser.Package;
@@ -757,6 +759,9 @@
     @GuardedBy("mPackages")
     final SparseArray<Map<String, Integer>> mChangedPackagesSequenceNumbers = new SparseArray<>();
 
+    @GuardedBy("mPackages")
+    final private ArraySet<PackageListObserver> mPackageListObservers = new ArraySet<>();
+
     class PackageParserCallback implements PackageParser.Callback {
         @Override public final boolean hasFeature(String feature) {
             return PackageManagerService.this.hasSystemFeature(feature, 0);
@@ -2095,6 +2100,10 @@
                 }
             }
 
+            if (allNewUsers && !update) {
+                notifyPackageAdded(packageName);
+            }
+
             // Log current value of "unknown sources" setting
             EventLog.writeEvent(EventLogTags.UNKNOWN_SOURCES_ENABLED,
                     getUnknownSourcesSettings());
@@ -12983,6 +12992,34 @@
         });
     }
 
+    @Override
+    public void notifyPackageAdded(String packageName) {
+        final PackageListObserver[] observers;
+        synchronized (mPackages) {
+            if (mPackageListObservers.size() == 0) {
+                return;
+            }
+            observers = (PackageListObserver[]) mPackageListObservers.toArray();
+        }
+        for (int i = observers.length - 1; i >= 0; --i) {
+            observers[i].onPackageAdded(packageName);
+        }
+    }
+
+    @Override
+    public void notifyPackageRemoved(String packageName) {
+        final PackageListObserver[] observers;
+        synchronized (mPackages) {
+            if (mPackageListObservers.size() == 0) {
+                return;
+            }
+            observers = (PackageListObserver[]) mPackageListObservers.toArray();
+        }
+        for (int i = observers.length - 1; i >= 0; --i) {
+            observers[i].onPackageRemoved(packageName);
+        }
+    }
+
     /**
      * Sends a broadcast for the given action.
      * <p>If {@code isInstantApp} is {@code true}, then the broadcast is protected with
@@ -17640,6 +17677,7 @@
                         removedPackage, extras,
                         Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND,
                         null, null, broadcastUsers, instantUserIds);
+                    packageSender.notifyPackageRemoved(removedPackage);
                 }
             }
             if (removedAppId >= 0) {
@@ -20395,10 +20433,9 @@
             }
         }
         sUserManager.systemReady();
-
         // If we upgraded grant all default permissions before kicking off.
         for (int userId : grantPermissionsUserIds) {
-            mDefaultPermissionPolicy.grantDefaultPermissions(mPackages.values(), userId);
+            mDefaultPermissionPolicy.grantDefaultPermissions(userId);
         }
 
         if (grantPermissionsUserIds == EMPTY_INT_ARRAY) {
@@ -22445,8 +22482,8 @@
     }
 
     void onNewUserCreated(final int userId) {
+        mDefaultPermissionPolicy.grantDefaultPermissions(userId);
         synchronized(mPackages) {
-            mDefaultPermissionPolicy.grantDefaultPermissions(mPackages.values(), userId);
             // If permission review for legacy apps is required, we represent
             // dagerous permissions for such apps as always granted runtime
             // permissions to keep per user flag state whether review is needed.
@@ -22933,6 +22970,29 @@
         }
 
         @Override
+        public PackageList getPackageList(PackageListObserver observer) {
+            synchronized (mPackages) {
+                final int N = mPackages.size();
+                final ArrayList<String> list = new ArrayList<>(N);
+                for (int i = 0; i < N; i++) {
+                    list.add(mPackages.keyAt(i));
+                }
+                final PackageList packageList = new PackageList(list, observer);
+                if (observer != null) {
+                    mPackageListObservers.add(packageList);
+                }
+                return packageList;
+            }
+        }
+
+        @Override
+        public void removePackageListObserver(PackageListObserver observer) {
+            synchronized (mPackages) {
+                mPackageListObservers.remove(observer);
+            }
+        }
+
+        @Override
         public PackageParser.Package getDisabledPackage(String packageName) {
             synchronized (mPackages) {
                 final PackageSetting ps = mSettings.getDisabledSystemPkgLPr(packageName);
@@ -23594,4 +23654,6 @@
         final IIntentReceiver finishedReceiver, final int[] userIds, int[] instantUserIds);
     void sendPackageAddedForNewUsers(String packageName, boolean sendBootCompleted,
         boolean includeStopped, int appId, int[] userIds, int[] instantUserIds);
+    void notifyPackageAdded(String packageName);
+    void notifyPackageRemoved(String packageName);
 }
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 01f3c57..d38dc9a 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -30,6 +30,7 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
+import android.content.pm.PackageList;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.PackageParser;
@@ -252,11 +253,11 @@
         }
     }
 
-    public void grantDefaultPermissions(Collection<PackageParser.Package> packages, int userId) {
+    public void grantDefaultPermissions(int userId) {
         if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_EMBEDDED, 0)) {
-            grantAllRuntimePermissions(packages, userId);
+            grantAllRuntimePermissions(userId);
         } else {
-            grantPermissionsToSysComponentsAndPrivApps(packages, userId);
+            grantPermissionsToSysComponentsAndPrivApps(userId);
             grantDefaultSystemHandlerPermissions(userId);
             grantDefaultPermissionExceptions(userId);
         }
@@ -278,10 +279,14 @@
         }
     }
 
-    private void grantAllRuntimePermissions(
-            Collection<PackageParser.Package> packages, int userId) {
+    private void grantAllRuntimePermissions(int userId) {
         Log.i(TAG, "Granting all runtime permissions for user " + userId);
-        for (PackageParser.Package pkg : packages) {
+        final PackageList packageList = mServiceInternal.getPackageList();
+        for (String packageName : packageList.getPackageNames()) {
+            final PackageParser.Package pkg = mServiceInternal.getPackage(packageName);
+            if (pkg == null) {
+                continue;
+            }
             grantRuntimePermissionsForPackage(userId, pkg);
         }
     }
@@ -290,10 +295,14 @@
         mHandler.sendEmptyMessage(MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS);
     }
 
-    private void grantPermissionsToSysComponentsAndPrivApps(
-            Collection<PackageParser.Package> packages, int userId) {
+    private void grantPermissionsToSysComponentsAndPrivApps(int userId) {
         Log.i(TAG, "Granting permissions to platform components for user " + userId);
-        for (PackageParser.Package pkg : packages) {
+        final PackageList packageList = mServiceInternal.getPackageList();
+        for (String packageName : packageList.getPackageNames()) {
+            final PackageParser.Package pkg = mServiceInternal.getPackage(packageName);
+            if (pkg == null) {
+                continue;
+            }
             if (!isSysComponentOrPersistentPlatformSignedPrivApp(pkg)
                     || !doesPackageSupportRuntimePermissions(pkg)
                     || pkg.requestedPermissions.isEmpty()) {
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
index e12a8da..cdac516 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
@@ -17,75 +17,93 @@
 package com.android.server.pm;
 
 import android.content.IIntentReceiver;
-
 import android.os.Bundle;
+import android.support.test.runner.AndroidJUnit4;
 
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.io.File;
 
 // runtest -c com.android.server.pm.PackageManagerServiceTest frameworks-services
-
-@SmallTest
-public class PackageManagerServiceTest extends AndroidTestCase {
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+// bit FrameworksServicesTests:com.android.server.pm.PackageManagerServiceTest
+@RunWith(AndroidJUnit4.class)
+public class PackageManagerServiceTest {
+    @Before
+    public void setUp() throws Exception {
     }
 
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
+    @After
+    public void tearDown() throws Exception {
     }
 
+    @Test
     public void testPackageRemoval() throws Exception {
-      class PackageSenderImpl implements PackageSender {
-        public void sendPackageBroadcast(final String action, final String pkg,
-            final Bundle extras, final int flags, final String targetPkg,
-            final IIntentReceiver finishedReceiver, final int[] userIds,
-            int[] instantUserIds) {
+        class PackageSenderImpl implements PackageSender {
+            public void sendPackageBroadcast(final String action, final String pkg,
+                    final Bundle extras, final int flags, final String targetPkg,
+                    final IIntentReceiver finishedReceiver, final int[] userIds,
+                    int[] instantUserIds) {
+            }
+
+            public void sendPackageAddedForNewUsers(String packageName,
+                    boolean sendBootComplete, boolean includeStopped, int appId,
+                    int[] userIds, int[] instantUserIds) {
+            }
+
+            @Override
+            public void notifyPackageAdded(String packageName) {
+            }
+
+            @Override
+            public void notifyPackageRemoved(String packageName) {
+            }
         }
 
-        public void sendPackageAddedForNewUsers(String packageName,
-            boolean sendBootComplete, boolean includeStopped, int appId,
-            int[] userIds, int[] instantUserIds) {
-        }
-      }
+        PackageSenderImpl sender = new PackageSenderImpl();
+        PackageSetting setting = null;
+        PackageManagerService.PackageRemovedInfo pri =
+                new PackageManagerService.PackageRemovedInfo(sender);
 
-      PackageSenderImpl sender = new PackageSenderImpl();
-      PackageSetting setting = null;
-      PackageManagerService.PackageRemovedInfo pri =
-          new PackageManagerService.PackageRemovedInfo(sender);
+        // Initial conditions: nothing there
+        Assert.assertNull(pri.removedUsers);
+        Assert.assertNull(pri.broadcastUsers);
 
-      // Initial conditions: nothing there
-      assertNull(pri.removedUsers);
-      assertNull(pri.broadcastUsers);
+        // populateUsers with nothing leaves nothing
+        pri.populateUsers(null, setting);
+        Assert.assertNull(pri.broadcastUsers);
 
-      // populateUsers with nothing leaves nothing
-      pri.populateUsers(null, setting);
-      assertNull(pri.broadcastUsers);
+        // Create a real (non-null) PackageSetting and confirm that the removed
+        // users are copied properly
+        setting = new PackageSetting("name", "realName", new File("codePath"),
+                new File("resourcePath"), "legacyNativeLibraryPathString",
+                "primaryCpuAbiString", "secondaryCpuAbiString",
+                "cpuAbiOverrideString", 0, 0, 0, "parentPackageName", null, 0,
+                null, null);
+        pri.populateUsers(new int[] {
+                1, 2, 3, 4, 5
+        }, setting);
+        Assert.assertNotNull(pri.broadcastUsers);
+        Assert.assertEquals(5, pri.broadcastUsers.length);
+        Assert.assertNotNull(pri.instantUserIds);
+        Assert.assertEquals(0, pri.instantUserIds.length);
 
-      // Create a real (non-null) PackageSetting and confirm that the removed
-      // users are copied properly
-      setting = new PackageSetting("name", "realName", new File("codePath"),
-          new File("resourcePath"), "legacyNativeLibraryPathString",
-          "primaryCpuAbiString", "secondaryCpuAbiString",
-          "cpuAbiOverrideString", 0, 0, 0, "parentPackageName", null, 0,
-          null, null);
-      pri.populateUsers(new int[] {1, 2, 3, 4, 5}, setting);
-      assertNotNull(pri.broadcastUsers);
-      assertEquals(5, pri.broadcastUsers.length);
+        // Exclude a user
+        pri.broadcastUsers = null;
+        final int EXCLUDED_USER_ID = 4;
+        setting.setInstantApp(true, EXCLUDED_USER_ID);
+        pri.populateUsers(new int[] {
+                1, 2, 3, EXCLUDED_USER_ID, 5
+        }, setting);
+        Assert.assertNotNull(pri.broadcastUsers);
+        Assert.assertEquals(4, pri.broadcastUsers.length);
+        Assert.assertNotNull(pri.instantUserIds);
+        Assert.assertEquals(1, pri.instantUserIds.length);
 
-      // Exclude a user
-      pri.broadcastUsers = null;
-      final int EXCLUDED_USER_ID = 4;
-      setting.setInstantApp(true, EXCLUDED_USER_ID);
-      pri.populateUsers(new int[] {1, 2, 3, EXCLUDED_USER_ID, 5}, setting);
-      assertNotNull(pri.broadcastUsers);
-      assertEquals(5 - 1, pri.broadcastUsers.length);
-
-      // TODO: test that sendApplicationHiddenForUser() actually fills in
-      // broadcastUsers
+        // TODO: test that sendApplicationHiddenForUser() actually fills in
+        // broadcastUsers
     }
 }
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index 973df31..176057d 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -15,8 +15,10 @@
  */
 package android.telephony.euicc;
 
+import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
 import android.app.Activity;
 import android.app.PendingIntent;
 import android.content.Context;
@@ -29,6 +31,9 @@
 
 import com.android.internal.telephony.euicc.IEuiccController;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * EuiccManager is the application interface to eUICCs, or eSIMs/embedded SIMs.
  *
@@ -167,6 +172,35 @@
      */
     public static final String META_DATA_CARRIER_ICON = "android.telephony.euicc.carriericon";
 
+    /**
+     * Euicc OTA update status which can be got by {@link #getOtaStatus}
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"EUICC_OTA_"}, value = {
+            EUICC_OTA_IN_PROGRESS,
+            EUICC_OTA_FAILED,
+            EUICC_OTA_SUCCEEDED,
+            EUICC_OTA_NOT_NEEDED,
+            EUICC_OTA_STATUS_UNAVAILABLE
+
+    })
+    public @interface OtaStatus{}
+
+    /**
+     * An OTA is in progress. During this time, the eUICC is not available and the user may lose
+     * network access.
+     */
+    public static final int EUICC_OTA_IN_PROGRESS = 1;
+    /** The OTA update failed. */
+    public static final int EUICC_OTA_FAILED = 2;
+    /** The OTA update finished successfully. */
+    public static final int EUICC_OTA_SUCCEEDED = 3;
+    /** The OTA update not needed since current eUICC OS is latest. */
+    public static final int EUICC_OTA_NOT_NEEDED = 4;
+    /** The OTA status is unavailable since eUICC service is unavailable. */
+    public static final int EUICC_OTA_STATUS_UNAVAILABLE = 5;
+
     private final Context mContext;
 
     /** @hide */
@@ -211,6 +245,26 @@
     }
 
     /**
+     * Returns the current status of eUICC OTA.
+     *
+     * <p>Requires the {@link android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission.
+     *
+     * @return the status of eUICC OTA. If {@link #isEnabled()} is false or the eUICC is not ready,
+     *     {@link OtaStatus#EUICC_OTA_STATUS_UNAVAILABLE} will be returned.
+     */
+    @SystemApi
+    public int getOtaStatus() {
+        if (!isEnabled()) {
+            return EUICC_OTA_STATUS_UNAVAILABLE;
+        }
+        try {
+            return getIEuiccController().getOtaStatus();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Attempt to download the given {@link DownloadableSubscription}.
      *
      * <p>Requires the {@link android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission,
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index e2d25b8..f804cb0 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -417,6 +417,8 @@
     int RIL_REQUEST_SET_CARRIER_INFO_IMSI_ENCRYPTION = 141;
     int RIL_REQUEST_START_NETWORK_SCAN = 142;
     int RIL_REQUEST_STOP_NETWORK_SCAN = 143;
+    int RIL_REQUEST_GET_SLOT_STATUS = 144;
+    int RIL_REQUEST_SET_LOGICAL_TO_PHYSICAL_SLOT_MAPPING = 145;
 
     int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
 
@@ -471,4 +473,5 @@
     int RIL_UNSOL_MODEM_RESTART = 1047;
     int RIL_UNSOL_CARRIER_INFO_IMSI_ENCRYPTION = 1048;
     int RIL_UNSOL_NETWORK_SCAN_RESULT = 1049;
+    int RIL_UNSOL_ICC_SLOT_STATUS = 1050;
 }
diff --git a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
index b3fc90d..0a0ad90 100644
--- a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
+++ b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
@@ -30,6 +30,7 @@
     oneway void getDefaultDownloadableSubscriptionList(
         String callingPackage, in PendingIntent callbackIntent);
     String getEid();
+    int getOtaStatus();
     oneway void downloadSubscription(in DownloadableSubscription subscription,
         boolean switchAfterDownload, String callingPackage, in PendingIntent callbackIntent);
     EuiccInfo getEuiccInfo();
diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java
index e3752ac..928a1da 100644
--- a/wifi/java/android/net/wifi/WifiScanner.java
+++ b/wifi/java/android/net/wifi/WifiScanner.java
@@ -160,6 +160,24 @@
      */
     public static final int REPORT_EVENT_NO_BATCH = (1 << 2);
 
+    /**
+     * This is used to indicate the purpose of the scan to the wifi chip in
+     * {@link ScanSettings#type}.
+     * On devices with multiple hardware radio chains (and hence different modes of scan),
+     * this type serves as an indication to the hardware on what mode of scan to perform.
+     * Only apps holding android.Manifest.permission.NETWORK_STACK permission can set this value.
+     *
+     * Note: This serves as an intent and not as a stipulation, the wifi chip
+     * might honor or ignore the indication based on the current radio conditions. Always
+     * use the {@link ScanResult#radioChainInfos} to figure out the radio chain configuration used
+     * to receive the corresponding scan result.
+     */
+    /** {@hide} */
+    public static final int TYPE_LOW_LATENCY = 0;
+    /** {@hide} */
+    public static final int TYPE_LOW_POWER = 1;
+    /** {@hide} */
+    public static final int TYPE_HIGH_ACCURACY = 2;
 
     /** {@hide} */
     public static final String SCAN_PARAMS_SCAN_SETTINGS_KEY = "ScanSettings";
@@ -193,7 +211,8 @@
          * list of hidden networks to scan for. Explicit probe requests are sent out for such
          * networks during scan. Only valid for single scan requests.
          * {@hide}
-         * */
+         */
+        @RequiresPermission(android.Manifest.permission.NETWORK_STACK)
         public HiddenNetwork[] hiddenNetworks;
         /** period of background scan; in millisecond, 0 => single shot scan */
         public int periodInMs;
@@ -223,6 +242,13 @@
          * {@hide}
          */
         public boolean isPnoScan;
+        /**
+         * Indicate the type of scan to be performed by the wifi chip.
+         * Default value: {@link #TYPE_LOW_LATENCY}.
+         * {@hide}
+         */
+        @RequiresPermission(android.Manifest.permission.NETWORK_STACK)
+        public int type = TYPE_LOW_LATENCY;
 
         /** Implement the Parcelable interface {@hide} */
         public int describeContents() {
@@ -239,6 +265,7 @@
             dest.writeInt(maxPeriodInMs);
             dest.writeInt(stepCount);
             dest.writeInt(isPnoScan ? 1 : 0);
+            dest.writeInt(type);
             if (channels != null) {
                 dest.writeInt(channels.length);
                 for (int i = 0; i < channels.length; i++) {
@@ -272,6 +299,7 @@
                         settings.maxPeriodInMs = in.readInt();
                         settings.stepCount = in.readInt();
                         settings.isPnoScan = in.readInt() == 1;
+                        settings.type = in.readInt();
                         int num_channels = in.readInt();
                         settings.channels = new ChannelSpec[num_channels];
                         for (int i = 0; i < num_channels; i++) {
diff --git a/wifi/java/android/net/wifi/rtt/ResponderConfig.java b/wifi/java/android/net/wifi/rtt/ResponderConfig.java
index 8be7782..c3e1007 100644
--- a/wifi/java/android/net/wifi/rtt/ResponderConfig.java
+++ b/wifi/java/android/net/wifi/rtt/ResponderConfig.java
@@ -37,7 +37,7 @@
  *
  * @hide (@SystemApi)
  */
-public class ResponderConfig implements Parcelable {
+public final class ResponderConfig implements Parcelable {
     private static final int AWARE_BAND_2_DISCOVERY_CHANNEL = 2437;
 
     /** @hide */
@@ -122,15 +122,14 @@
     /**
      * The MAC address of the Responder. Will be null if a Wi-Fi Aware peer identifier (the
      * peerHandle field) ise used to identify the Responder.
-     * TODO: convert to MacAddress
      */
-    public MacAddress macAddress;
+    public final MacAddress macAddress;
 
     /**
      * The peer identifier of a Wi-Fi Aware Responder. Will be null if a MAC Address (the macAddress
      * field) is used to identify the Responder.
      */
-    public PeerHandle peerHandle;
+    public final PeerHandle peerHandle;
 
     /**
      * The device type of the Responder.
@@ -171,7 +170,7 @@
     public final int preamble;
 
     /**
-     * Constructs Responder configuration.
+     * Constructs Responder configuration, using a MAC address to identify the Responder.
      *
      * @param macAddress      The MAC address of the Responder.
      * @param responderType   The type of the responder device, specified using
@@ -210,7 +209,7 @@
     }
 
     /**
-     * Constructs Responder configuration.
+     * Constructs Responder configuration, using a Wi-Fi Aware PeerHandle to identify the Responder.
      *
      * @param peerHandle      The Wi-Fi Aware peer identifier of the Responder.
      * @param responderType   The type of the responder device, specified using
@@ -245,6 +244,45 @@
     }
 
     /**
+     * Constructs Responder configuration. This is an internal-only constructor which specifies both
+     * a MAC address and a Wi-Fi PeerHandle to identify the Responder.
+     *
+     * @param macAddress      The MAC address of the Responder.
+     * @param peerHandle      The Wi-Fi Aware peer identifier of the Responder.
+     * @param responderType   The type of the responder device, specified using
+     *                        {@link ResponderType}.
+     * @param supports80211mc Indicates whether the responder supports IEEE 802.11mc.
+     * @param channelWidth    Responder channel bandwidth, specified using {@link ChannelWidth}.
+     * @param frequency       The primary 20 MHz frequency (in MHz) of the channel of the Responder.
+     * @param centerFreq0     Not used if the {@code channelWidth} is 20 MHz. If the Responder uses
+     *                        40, 80 or 160 MHz, this is the center frequency (in MHz), if the
+     *                        Responder uses 80 + 80 MHz, this is the center frequency of the first
+     *                        segment (in MHz).
+     * @param centerFreq1     Only used if the {@code channelWidth} is 80 + 80 MHz. If the
+     *                        Responder
+     *                        uses 80 + 80 MHz, this is the center frequency of the second segment
+     *                        (in
+     *                        MHz).
+     * @param preamble        The preamble used by the Responder, specified using
+     *                        {@link PreambleType}.
+     * @hide
+     */
+    public ResponderConfig(@NonNull MacAddress macAddress, @NonNull PeerHandle peerHandle,
+            @ResponderType int responderType, boolean supports80211mc,
+            @ChannelWidth int channelWidth, int frequency, int centerFreq0, int centerFreq1,
+            @PreambleType int preamble) {
+        this.macAddress = macAddress;
+        this.peerHandle = peerHandle;
+        this.responderType = responderType;
+        this.supports80211mc = supports80211mc;
+        this.channelWidth = channelWidth;
+        this.frequency = frequency;
+        this.centerFreq0 = centerFreq0;
+        this.centerFreq1 = centerFreq1;
+        this.preamble = preamble;
+    }
+
+    /**
      * Creates a Responder configuration from a {@link ScanResult} corresponding to an Access
      * Point (AP), which can be obtained from {@link android.net.wifi.WifiManager#getScanResults()}.
      */
diff --git a/wifi/tests/src/android/net/wifi/WifiScannerTest.java b/wifi/tests/src/android/net/wifi/WifiScannerTest.java
index e542789..a4366d4 100644
--- a/wifi/tests/src/android/net/wifi/WifiScannerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiScannerTest.java
@@ -16,19 +16,23 @@
 
 package android.net.wifi;
 
+import static org.junit.Assert.assertEquals;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.validateMockitoUsage;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.os.Handler;
+import android.os.Parcel;
 import android.os.test.TestLooper;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.net.wifi.WifiScanner.ScanSettings;
 
 import com.android.internal.util.test.BidirectionalAsyncChannelServer;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Test;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -70,4 +74,50 @@
         validateMockitoUsage();
     }
 
+    /**
+     * Verify parcel read/write for ScanSettings.
+     */
+    @Test
+    public void verifyScanSettingsParcelWithBand() throws Exception {
+        ScanSettings writeSettings = new ScanSettings();
+        writeSettings.type = WifiScanner.TYPE_LOW_POWER;
+        writeSettings.band = WifiScanner.WIFI_BAND_BOTH_WITH_DFS;
+
+        ScanSettings readSettings = parcelWriteRead(writeSettings);
+        assertEquals(readSettings.type, writeSettings.type);
+        assertEquals(readSettings.band, writeSettings.band);
+        assertEquals(0, readSettings.channels.length);
+    }
+
+    /**
+     * Verify parcel read/write for ScanSettings.
+     */
+    @Test
+    public void verifyScanSettingsParcelWithChannels() throws Exception {
+        ScanSettings writeSettings = new ScanSettings();
+        writeSettings.type = WifiScanner.TYPE_HIGH_ACCURACY;
+        writeSettings.band = WifiScanner.WIFI_BAND_UNSPECIFIED;
+        writeSettings.channels = new WifiScanner.ChannelSpec[] {
+                new WifiScanner.ChannelSpec(5),
+                new WifiScanner.ChannelSpec(7)
+        };
+
+        ScanSettings readSettings = parcelWriteRead(writeSettings);
+        assertEquals(writeSettings.type, readSettings.type);
+        assertEquals(writeSettings.band, readSettings.band);
+        assertEquals(2, readSettings.channels.length);
+        assertEquals(5, readSettings.channels[0].frequency);
+        assertEquals(7, readSettings.channels[1].frequency);
+    }
+
+    /**
+     * Write the provided {@link ScanSettings} to a parcel and deserialize it.
+     */
+    private static ScanSettings parcelWriteRead(ScanSettings writeSettings) throws Exception {
+        Parcel parcel = Parcel.obtain();
+        writeSettings.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);    // Rewind data position back to the beginning for read.
+        return ScanSettings.CREATOR.createFromParcel(parcel);
+    }
+
 }